原生JS实现自动无缝切换轮播图(可鼠标拖动)

^ _ ^ 最近参加了一个免费的华为在线课程,有一个作业是用原生js实现轮播图。练手的同时发现工作中框架用的多。原生JS不如以前那么熟练了。特此写这篇文章记录一下。希望能帮助到一些新手盆友。来一起学习吧。

目录

  • 功能需求
  • 实现思路
    • html & CSS结构分析
    • JS逻辑实现
      • 自动无缝切换
      • 点击小点切换对应图片
      • 鼠标拖动切换图片
      • 浏览器窗口尺寸改变不影响以上3个功能
  • 所有源码

功能需求

实现以下4个需求:

  1. 自动无缝切换图片

  2. 鼠标左右拖动切换图片

  3. 点击小点切换对应图片

  4. 浏览器窗口尺寸改变不影响以上功能

实现思路

html & CSS结构分析

  1. 清除浏览器默认样式和设置基础样式
*{
     
	box-sizing: border-box;
}
body, ul{
     
	margin: 0;
	padding: 0;
	font-family: PingFang SC,PingFangSC-Regular,Helvetica Neue,Microsoft YaHei Regular,Microsoft YaHei,宋体,"sans-serif";
	font-size: 16px;
}
  1. 设置一个外层容器
<div class= page”>
.page{
     
	overflow: hidden; //让视线范围内只显示一张图片
	position: relative; //用于点的定位
}
  1. 设置一个内层容器
<div class=”box”>
.box{
     
	display: flex; //设置flex布局,默认从左到右依次排列。
}

不清楚flex的盆友可以看一下阮大神的文章:Flex 布局教程:语法篇

  1. 设置一个包住a链接和img图片的容器
<div class="slideWrapper"> <a href="xxx" target="_blank">a>div>
.slideWrapper{
     
	width: 100%;
   	height: 100%;
   	flex-shrink: 0;
}

需要注意的是除了需要设置宽高100%以外,还有设置flex-shrink: 0; 不让容器等比例缩小

  1. 设置点击小点的样式
.dots{
     
	position: absolute;
	bottom: 45px;
	left: 50%;
	transform: translateX(-50%);
}
.dots span{
     
	margin: 0 4px;
	width: 8px;
	height: 8px;
	display: inline-block;
	border-radius: 100%;
	background: #000;
	opacity: .2;
	text-indent: -9999px;
}
span.active {
     
	width: 28px!important;
	background: #5e7ce0!important;
	border-radius: 5px!important;
	opacity: 1;
}

JS逻辑实现

自动无缝切换

第一步、创建函数,通过修改内层容器“box” 的transform: translateX(?px)来切换显示图片。

let boxDom = document.querySelector('.box');
function changeBoxDomStyle(offset, duration = '0ms') {
     
	boxDom.style.transform = `translateX(${
       offset}px)`;
	boxDom.style.transitionDuration = duration;
}

第二步、计算当前所需偏移量,并调用设置偏移量函数carousel ,调用设置小点激活状态函数changeDotsStyle

技巧

  • 每一次的偏移量就是图片宽度 * 索引 moveWith * index
  • 表面上8张图片,实际上是 8副本 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 1副本 排序,索引是0 - 9. 总共10张图片。当index = 9 也就是切换到第一张图片副本的时候,迅速切换到真正的第一张图片。因为没有动画时间,所以用户感受不到切换。
    注意一定要放到 boxDom.addEventListener('transitionend', () => {})才能保证从8 -> 1副本能够顺利执行完了,再无痕迹的切换到真正的第一张图片。至于不放在这里面直接在赋值新的style会出什么副作用,就动手试试吧。
  • 转换图片的同时,也要把对应索引的小点的样式设置成active的样式
let index = 1;
let imgLen = document.querySelectorAll('.slide').length;
let moveWith = document.querySelector('.slide').offsetWidth;
let currentOffset =  moveWith;
const DEFAULT_DURATION = '300ms';
function carousel() {
     
	//当切换到1副本的时候,迅速切换到真正的第一张图片
	boxDom.addEventListener('transitionend', () => {
     
		if(index === imgLen - 1){
     
			index = 1;
			currentOffset = moveWith;
			changeBoxDomStyle(-currentOffset);
		}
	});
	//
	if(index < imgLen - 1){
     
		index++;
		changeDotsStyle();
		currentOffset = moveWith * index;
		changeBoxDomStyle(-currentOffset, DEFAULT_DURATION);
	}
}

function changeDotsStyle() {
     
		document.querySelector('.dot_item.active').classList.remove('active');
		if(index === imgLen - 1){
     
			dotItems[0].classList.add('active');
		}else{
     
			dotItems[index - 1].classList.add('active');
		}
	}

第三步、创建定时器调用函数carousel

const ANIMATION_INTERVAL = 3000;
function autoPlay() {
     
	if(isAutoPlay){
      return; }
	isAutoPlay = true;
	animate = setInterval(carousel, ANIMATION_INTERVAL);
}

点击小点切换对应图片

给每个小点绑定点击事件,获取当前的索引,调用设置偏移量函数carousel。
注: 别忘了先停止定时器,再重起定时器

function dotsControl() {
     
	for(dot of dotItems){
     
		dot.addEventListener("click", (e) => {
     
			stopAutoPlay();
			index = e.target.getAttribute('data-dots-index') - 1;
			carousel();
			autoPlay();
		});
	}	
}
function stopAutoPlay() {
     
	if(!isAutoPlay){
      return; }
	isAutoPlay = false;
	clearInterval(animate);
}

鼠标拖动切换图片

这是需求里面最难的。涉及多个鼠标事件,还有一个小坑
第一步、鼠标按下时,停止定时器。获取当前的X坐标currentX, 修改isKeyDown 为true。

let isKeyDown = false;
boxDom.addEventListener('mousedown', (e) => {
     
	stopAutoPlay();
	isKeyDown = true;
	isDragingImg = false;
	currentX = e.clientX;
});

第二步、鼠标移动时计算与最初的currentX相比坐标偏移量,调用changeBoxDomStyle

boxDom.addEventListener('mousemove', (e) => {
     
	if(isKeyDown){
     
		moveX = e.clientX - currentX;
		let moveOffset = moveX - currentOffset;
		changeBoxDomStyle(moveOffset);
		isDragingImg = true;
	}
});

第三步、鼠标抬起时,判断偏移量是否超过一半Math.abs(moveX) > minMoveOffset,如果超过一半,就相应的往前moveX > 0或者往后移动一个图片的距离。如果不够,那么就恢复原本的偏移量。保持之前的图片。最后恢复定时器。

let minMoveOffset = moveWith / 2;
document.onmouseup = function() {
     
	isKeyDown = false;
	if(Math.abs(moveX) > minMoveOffset) {
     
		if(moveX > 0) {
     
			//移动到前一个图片
			if(index === 1){
     
				//移动到真正的图片8
				index = imgLen - 2;
				currentOffset = index * moveWith;
			}else{
     
				index = index - 1;
				currentOffset = currentOffset - moveWith;
			}
		}else {
     
			//移动到后一个图片
			currentOffset = currentOffset + moveWith;
			index = index + 1;
			if(index === imgLen -1) {
     
				//移动到真正的图片1
				currentOffset = moveWith;
				index = 1;
			}
		}
		changeDotsStyle();
	}
	changeBoxDomStyle(-currentOffset, '300ms');
	autoPlay();
}

第四步、解决小坑
写完以上代码,你会发现mousemove只能获取几个值,会有个缩小版的图片跟着鼠标。这个时候需要禁止图片拖拽。

function stopImgDrag() {
     
	let slideImgs = document.querySelectorAll('img.slide');
	for (img of slideImgs) {
     
		img.ondragstart = () => {
     
			return false;
		}
	}
}

第五步、在拖拽的时候禁止 a 标签的跳转

function linkClickContr() {
     
	let aNodeList = document.querySelectorAll('.slideWrapper > a');
	for(let i of aNodeList)
	i.addEventListener('click', (e) => {
     
		if(isDragingImg) {
      //isDragingImg在mousemove里面设置成了true
			e.preventDefault();
		}
	});
}

浏览器窗口尺寸改变不影响以上3个功能

监听window的resize,重设偏移量

window.addEventListener("resize", () => {
     
	stopAutoPlay();
	moveWith = document.querySelector('.slide').offsetWidth;
	minMoveOffset = moveWith / 2;
	currentOffset = index * moveWith;
	changeBoxDomStyle(-currentOffset);
	autoPlay();
}, false);

所有源码

直接复制,存成xxx.html就可以看效果了。希望对路过的小伙伴有所帮助。也欢迎留言一起探讨技术的疑难杂症。因为用的是华为线上的图片。如果后面来的盆友发现加载有问题,随便上网找几张图替换就可以了。


<html>
<head>
	<meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>轮播图title>
head>
<style type="text/css">
	*{
      
		box-sizing: border-box;
	}
	body, ul{
      
		margin: 0;
		padding: 0;
		font-family: PingFang SC,PingFangSC-Regular,Helvetica Neue,Microsoft YaHei Regular,Microsoft YaHei,宋体,"sans-serif";
		font-size: 16px;
	}
	.page{
      
		overflow: hidden;
		position: relative;
	}
	.box{
      
		display: flex;
	}
	.slideWrapper{
      
		width: 100%;
    	height: 100%;
    	flex-shrink: 0;
	}
	img{
      
		width: 100%;
		height: 100%;
	}
	.dots{
      
		position: absolute;
		bottom: 45px;
		left: 50%;
		transform: translateX(-50%);
	}
	.dots span{
      
		margin: 0 4px;
		width: 8px;
		height: 8px;
		display: inline-block;
		border-radius: 100%;
		background: #000;
		opacity: .2;
		text-indent: -9999px;
	}
	span.active {
      
		width: 28px!important;
		background: #5e7ce0!important;
		border-radius: 5px!important;
		opacity: 1;
		outline: none;
	}
	.section_wrapper {
      
		width: 1200px;
		margin: 0 auto ;
		position: relative;
		top: -25px;
		background: #fff;
		box-shadow: 0 0 20px 0 rgba(41,48,64,.1);
		border-radius: 5px;
		padding: 30px;
		display: flex;
		align-items: center;
	}
	.section_leftTxt {
      
		padding-right: 40px;
	    color: #5e6678;
	    line-height: 32px;
	}
	.section_rightLink {
      
	    text-decoration: none;
	    height: 32px;
	    line-height: 32px;
	    min-width: 96px;
	    background-color: #5e7ce0;
	    color: #fff;
	    font-size: 14px;
	    border-radius: 16px;
	    text-align: center;
	}
	.section_rightLink:hover,
	.section_rightLink:focus {
      
		background-color: #526ecc;
	}
style>
<script type="text/javascript">
	let index = 1;
	let moveWith, dotItems, animate, currentX, moveX, currentOffset, minMoveOffset, imgLen, boxDom, isAutoPlay;
	let isKeyDown = false;
	let isDragingImg = false;
	const DEFAULT_DURATION = '300ms';
	const ANIMATION_INTERVAL = 3000;

	window.onload = function() {
      
		//initialize - set variables value
	 	imgLen = document.querySelectorAll('.slide').length;
		moveWith = document.querySelector('.slide').offsetWidth;

		currentOffset =  moveWith;
		minMoveOffset = moveWith / 2;
		dotItems = document.querySelectorAll('.dot_item');
		boxDom = document.querySelector('.box');

		//initailize - switch to first slide and make slide 8 backup visible
		changeBoxDomStyle(-moveWith);
		document.querySelectorAll('.slideWrapper')[0].style.visibility = 'visible';

		autoPlay();
		dotsControl();
		mouseMoveImg();
		stopImgDrag();
		linkClickContr();

        //reset parameter and offsetX when window resize
		window.addEventListener("resize", () => {
      
			stopAutoPlay();
			moveWith = document.querySelector('.slide').offsetWidth;
			minMoveOffset = moveWith / 2;
			currentOffset = index * moveWith;
			changeBoxDomStyle(-currentOffset);
			autoPlay();
		}, false);
	}

	function changeBoxDomStyle(offset, duration = '0ms') {
      
		boxDom.style.transform = `translateX(${
        offset}px)`;
		boxDom.style.transitionDuration = duration;
	}
	function carousel() {
      
		boxDom.addEventListener('transitionend', () => {
      
			if(index === imgLen - 1){
      
				index = 1;
				currentOffset = moveWith;
				changeBoxDomStyle(-currentOffset);
			}
		});

		if(index < imgLen - 1){
      
			index++;
			changeDotsStyle();
			currentOffset = moveWith * index;
			changeBoxDomStyle(-currentOffset, DEFAULT_DURATION);
		}
	}
	function dotsControl() {
      
		for(dot of dotItems){
      
			dot.addEventListener("click", (e) => {
      
				stopAutoPlay();
				index = e.target.getAttribute('data-dots-index') - 1;
				carousel();
				autoPlay();
			});
		}	
	}
	function stopAutoPlay() {
      
		//only auto play active then clearInterval in case extra action
		if(!isAutoPlay){
       return; }
		isAutoPlay = false;
		clearInterval(animate);
	}
	function autoPlay() {
      
		//only auto play stop then setInterval in case extra action
		if(isAutoPlay){
       return; }
		isAutoPlay = true;
		animate = setInterval(carousel, ANIMATION_INTERVAL);
	}
	function stopImgDrag() {
      
		let slideImgs = document.querySelectorAll('img.slide');
		for (img of slideImgs) {
      
			img.ondragstart = () => {
      
				return false;
			}
		}
	}
	function changeDotsStyle() {
      
		document.querySelector('.dot_item.active').classList.remove('active');
		if(index === imgLen - 1){
      
			dotItems[0].classList.add('active');
		}else{
      
			dotItems[index - 1].classList.add('active');
		}
	}
	function mouseMoveImg() {
      
		boxDom.addEventListener('mousedown', (e) => {
      
			stopAutoPlay();
			isKeyDown = true;
			isDragingImg = false;
			currentX = e.clientX;
		});
		boxDom.addEventListener('mousemove', (e) => {
      
			if(isKeyDown){
      
				moveX = e.clientX - currentX;
				let moveOffset = moveX - currentOffset;
				changeBoxDomStyle(moveOffset);
				isDragingImg = true;
			}
		});
		document.onmouseup = function() {
      
			isKeyDown = false;
			if(Math.abs(moveX) > minMoveOffset) {
      
				if(moveX > 0) {
      
					//move back previous img
					if(index === 1){
      
						//back to slide 8
						index = imgLen - 2;
						currentOffset = index * moveWith;
					}else{
      
						index = index - 1;
						currentOffset = currentOffset - moveWith;
					}
				}else {
      
					//move forward next img
					currentOffset = currentOffset + moveWith;
					index = index + 1;
					if(index === imgLen -1) {
      
						//move forward to slide 1
						currentOffset = moveWith;
						index = 1;
					}
				}
				changeDotsStyle();
			}
			changeBoxDomStyle(-currentOffset, '300ms');
			autoPlay();
		}
	}
	//stop link redirect when customer use mouse move
	function linkClickContr() {
      
		let aNodeList = document.querySelectorAll('.slideWrapper > a');
		for(let i of aNodeList)
		i.addEventListener('click', (e) => {
      
			if(isDragingImg) {
      
				e.preventDefault();
			}
		});
	}
script>
<body>
	<div class="page">
		<div class="box">
			<div class="slideWrapper" style="visibility: hidden;"><a href="https://developer.huaweicloud.com/activity/full-stack/java-developer.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-8.d14241daf518717981c6.jpg" alt="8"/>a>div>
			<div class="slideWrapper"><a href="https://bbs.huaweicloud.com/videos/102379" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-1.fdbf82d4c2ca7fad3225.jpg" alt="1"/>a>div>
			<div class="slideWrapper"><a href="https://bbs.huaweicloud.com/forum/thread-39737-1-1.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-2.64b1407e7a8db89d6cf2.jpg" alt="2"/>a>div>
			<div class="slideWrapper"><a href="https://mp.weixin.qq.com/s/YRfQ41-IoXZtkj-i27US_w" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-3.ce76c93c7a8a149ce2a2.jpg" alt="3"/>a>div>
			<div class="slideWrapper"><a href="https://developer.huaweicloud.com/activity/full-stack/web-developer.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-4.4ac0f6534a11844638e4.jpg" alt="4"/>a>div>
			<div class="slideWrapper"><a href="https://classroom.devcloud.huaweicloud.com/joinclass/bb9a534bd1bd49e191911bf72b815b91/1" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-5.0e9bb68d425a4a5c3b18.jpg" alt="5"/>a>div>
			<div class="slideWrapper"><a href="https://classroom.devcloud.huaweicloud.com/joinclass/2769fd5d78d14f2f98e80ff3d460c925/1" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-6.80cafe61bcb20a98eb1e.jpg" alt="6"/>a>div>
			<div class="slideWrapper"><a href="https://developer.huaweicloud.com/activity/reading/reading_pragmatic.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-7.d315f5241113c1fd9035.jpg" alt="7"/>a>div>
			<div class="slideWrapper"><a href="https://developer.huaweicloud.com/activity/full-stack/java-developer.html" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-8.d14241daf518717981c6.jpg" alt="8"/>a>div>
			<div class="slideWrapper"><a href="https://bbs.huaweicloud.com/videos/102379" target="_blank"><img class="slide" src="https://res.devcloud.huaweicloud.com/obsdevui/diploma/8.1.17.002/banner-1.fdbf82d4c2ca7fad3225.jpg" alt="1"/>a>div>
		div>
		<div class="dots">
			<span class="dot_item active" tabindex="0" role="button" aria-label="Go to slide 1" data-dots-index="1">span>
			<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 2" data-dots-index="2">span>
			<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 3" data-dots-index="3">span>
			<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 4" data-dots-index="4">span>
			<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 5" data-dots-index="5">span>
			<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 6" data-dots-index="6">span>
			<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 7" data-dots-index="7">span>
			<span class="dot_item" tabindex="0" role="button" aria-label="Go to slide 8" data-dots-index="8">span>
		div>
	div>
	<section class="section_wrapper">
		<div class="section_leftTxt">
			 Classroom是基于华为云的云上软件教学服务,支持高校师生实现备课、上课、作业、考试、实验、实训等全教学流程的线上教学,提供多类习题自动判题、企业级DevOps实训、免费在线习题库等众多高级特性辅助进行数字化教学转型
		div>
		<a href="https://classroom.devcloud.huaweicloud.com/platform" target="_blank" class="section_rightLink">开始上课a>
	section>
body>
html>

希望路过的,可爱的小姐姐,帅气的小哥哥多留言,多点赞,多收藏。有什么疑难杂症欢迎留言。
原生JS实现自动无缝切换轮播图(可鼠标拖动)_第1张图片

你可能感兴趣的:(前端,html,javascript,css3,前端,css)