原生JS:响应式轮播图

要求

  1. 图片自行滚动(规定自左向右滚动)
  2. 点击左右箭头,实现图片翻页;
  3. 点击提示圆点,显示不同图片;
  4. 滚动、翻页和显示都需要过渡效果;
  5. 响应式:轮播图随着浏览器窗口大小变化而变化;
  6. 功能整合。

最终效果

点击预览:全屏响应式轮播图


整体思路

之前也写过轮播图,那时候是WEB里的一个js作业,懵了半天,最后靠自己独立写出来了,不过思路不是很清晰,有点浆糊的感觉。而这一次是组队PROJECT,要我写一个页面的全屏轮播并响应浏览器大小

之前也搜索了其他人写的轮播图,参考了一些网站的轮播是怎么根据浏览器窗口大小响应的,发现可以从最基本的点击事件出发:

左右点击翻页是最基本的操作封装到一个next_pic()函数里,定时器可以周期执行这个函数来实现自动轮播,而显示提示点以及提示点和当前图片的联系就小菜一碟了。


具体实现

HTML文档结构

<!-- 轮播图 -->
<div id="content">
    <div id="carousel_wrap">
        <div id="carousel_images">
            <!-- 前后分别加上一张图片,方便无缝过渡显示。可以使用JS DOM增加节点操作省去该步骤 -->
            <img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
            <img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
            <img src="https://s2.ax1x.com/2019/06/05/VNGoZt.jpg">
            <img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
            <img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
        </div>
        <span class="arrow left-arrow">&lt;</span>
        <span class="arrow right-arrow">&gt;</span>
        <div id="dots">
            <!-- 使用小点标记实际多少张图片,要添加图片时需要修改carousel_images和此处 -->
            <span class="dot active"></span>
            <span class="dot"></span>
            <span class="dot"></span>
        </div>
    </div>
    <div id="test">
        <h1>blog</h1>
        <h1>blogs</h1>
        <h1>blog</h1>
        <h1>blog</h1>
    </div>
</div>
<!-- END 轮播图 -->

CSS样式

将“装着”轮播图片的carousel_images中的white-space属性设置为:nowrap,意为当到达容器边界时不换行,配合overflow实现carousel_wrap之外的图片都隐藏掉。

这里提到white-space属性:用来设置element元素对内容中的空格的处理方式,有着几个可选值:normal,nowrap,pre,pre-wrap,pre-line。没有设置white-space属性,则默认为white-space:normal。

区别这几个属性值关键词:源码空格源码换行容器大小换行。源码换行分为(1)br标签换行和(2)源码换行符换行,而(1)是全都符合的,所以区别就在于源码中的换行是否有效,容器大小换行就是根据容器自适应换行。因此在关键一点就变成->合并/保留空格、换行/不换行

  1. normal表示合并空格、换行,多个相邻空格合并成一个空格,在源码中的换行作为空格处理,只会根据容器的大小进行自动换行

  2. nowrap合并空格、不换行,在源码中的换行作为空格处理,但是不会根据容器大小换行,表示不换行

常与overflow配合使用,如:

overflow: hidden;
text-overflow: ellipsis; 表示文本省略号
  1. pre表示保留空格、不换行,保持源码中的空格,有几个空格算几个空格显示,同时换行只认源码中的换行和br标签,不会去自适应容器换行。

也就是说pre中的文本在源码中是什么样子,到网页中就是什么样子。

  1. pre-wrap表示保留空格、换行,保留空格,并且除了碰到源码中的换行和
    会换行外,还会自适应容器的边界进行换行。

  2. pre-line比较特殊,表示合并空格、换行,合并空格,换行和white-space:pre-wrap一样,遇到源码中的换行和br会换行,碰到容器的边界也会换行。

white-space:pre-line会保留源码中的换行,但是不会保留源码中的空格


回到轮播当中,发现使用了nowrap之后,图片之间有间隙,可以使用font-size: 0;清除缝隙。

body {
	overflow: scroll;
}

/* 轮播图样式表 */

#content #carousel_wrap {
	position: relative;
	margin: 0 auto;
	width: 100%; /* 轮播图宽度 */
	overflow: hidden;
}

#content #carousel_wrap #carousel_images {
	position: absolute;
	border: 0;
	outline: none;
	white-space: nowrap; /* 将图片一行排列 */
	width: 100%;
	font-size: 0; /* 清除white-space间隙 */
	margin: 0px; 
}

#content #carousel_wrap #carousel_images img {
	width: 100%;
}

#content #carousel_wrap .arrow {
	position: absolute;
	font-weight: bold;
	font-size: 50px;
	color: lightgray;
	top: 50%;
	transform: translateY(-50%);
	cursor: pointer;
	transition-property: opacity;
	transition-duration: 0.5s;
}

#content #carousel_wrap .arrow:hover {
	opacity: 0.5;
}

#content #carousel_wrap .left-arrow {
	left: 20px;
}

#content #carousel_wrap .right-arrow {
	right: 20px;
}

#content #carousel_wrap #dots {
	position: absolute;
	bottom: 20px;
	left: 50%;
	transform: translateX(-50%)
}

#content #carousel_wrap .dot {
	background-color: white;
	display: inline-block;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	margin: 4px;
	opacity: 0.2;
	cursor: pointer;
}

#content #carousel_wrap .active {
	opacity: 1;
}

.transition {
	transition-property: left;
	transition-duration: 1s;
}
/* END 轮播图样式表 */

JS

有了好的设计思路,轮播图的JS就不难写了。
DOM操作定位到具体元素:

var carouImg = document.getElementById("carousel_images");
var carouWrap = document.getElementById("carousel_wrap");
var img = carouImg.getElementsByTagName("img")[0];
var leftArrow = document.getElementsByClassName("left-arrow")[0];
var rightArrow = document.getElementsByClassName("right-arrow")[0];
var oBtn = document.getElementsByClassName("dot");
var index = 0; //标记当前图片
var index_length = oBtn.length; //图片数量

响应浏览器

为了能响应浏览器的大小来全屏显示轮播图片,让图片的宽占据浏览器宽的百分比值,图片的大小不定,那么轮播判断位置的时候就要使用clientWidth获取宽度。

关于响应式,要实现响应式,关键要对浏览器窗口的变化做出监听,使用onresize事件监听浏览器窗口变化,使用offsetWidth获取窗口的宽度。

同时为了代码的通用性,方便以后添加图片,不要把图片总数量的确切数值写在代码里,可以用提示点代表一张图片,用length获取总数。

涉及临界情况时,要考虑图片的位置以及过渡效果的影响。

动态获取绝对定位轮播图的高度,设置carousel_wrap的高度,宽度为整个main宽度。监听body大小变化,修改轮播图的图片位置和高度:

// 动态获取绝对定位轮播图的高度,设置carousel_wrap的高度,宽度为整个main宽度
	// 如果mystyle.css中使用overflow:auto->含有滚动条宽度; 故使用overflow:scroll
	carouImg.style.left = -img.clientWidth + "px"; 
	console.log(carouImg.style.left);
	carouWrap.style.height = img.offsetHeight + "px";

	// 监听body大小变化,修改轮播图的图片位置和高度
	document.body.onresize = function () { 
		carouImg.style.left = -img.clientWidth + "px";
		carouWrap.style.height = img.offsetHeight + "px";
	}

点击左右箭头翻页

next_pic()函数:

// 下一张图片
	function next_pic() {
		var left = parseInt(carouImg.style.left);
		if (left <= (-img.clientWidth) * (index_length + 1)) {
			// 临界情况判断
			carouImg.classList.remove("transition");
			var newLeft = -img.clientWidth * 1;
			carouImg.style.left = newLeft + 'px';
			newLeft = -img.clientWidth * 2;
			carouImg.classList.add("transition");
			index = 1;
		}
		else {
			// 一般情况
			var newLeft = parseInt(carouImg.style.left) - img.clientWidth;
			(index == (index_length - 1)) ? index = 0 : index += 1;
		}
		carouImg.style.left = newLeft + 'px'; // 不要忘记添加'px'
		console.log(newLeft);
	}

pre_pic()函数:

// 上一张图片
	function pre_pic() {
		var left = parseInt(carouImg.style.left);
		if (left >= -10) {
			// 临界情况判断
			carouImg.classList.remove("transition");
			var newLeft = -img.clientWidth * index_length;
			carouImg.style.left = newLeft + 'px';
			newLeft = -img.clientWidth * (index_length - 1);
			carouImg.classList.add("transition");
			index = index_length - 2;
		}
		else {
			// 一般情况
			var newLeft = parseInt(carouImg.style.left) + img.clientWidth;
			(index == 0) ? index = (index_length - 1) : index -= 1;
		}
		carouImg.style.left = newLeft + 'px';
		console.log(newLeft);
	}

显示提示点

通过对CSS类的增删操作来实现提示点样式的变化。

    function showCurrentDot(index) {
		for (let i = 0; i < oBtn.length; ++ i) {
			(i == index) ? oBtn[i].classList.add("active") : oBtn[i].classList.remove("active");
		}
	}

无缝过渡

同样通过对类的增删操作,用transition属性实现过渡动画效果。

// 给图片添加过渡效果
	carouImg.classList.add("transition");

定时器自动翻转

设置定时器实现自动轮播,添加鼠标移入移出监听事件

// 设置轮播定时器
	var timer = setInterval(function(){
		next_pic();
		showCurrentDot(index);
	}, 3000);

	carouWrap.onmouseover = function() {
		clearInterval(timer);
	}

	carouWrap.onmouseout = function() {
		timer = setInterval(function () {
			next_pic();
			showCurrentDot(index);
		}, 3000);
	}

代码整合

html

{% fold %}

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Lifeblog.com</title>
    <script type="text/javascript" src="js/friends.js"></script>
    <link rel="stylesheet" type="text/css" href="css/friends.css" />
</head>

<body>
    <!-- 轮播图 -->
    <div id="content">
        <div id="carousel_wrap">
            <div id="carousel_images">
                <!-- 前后分别加上一张图片,方便无缝过渡显示。可以使用JS DOM增加节点操作省去该步骤 -->
                <img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
                <img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
                <img src="https://s2.ax1x.com/2019/06/05/VNGoZt.jpg">
                <img src="https://s2.ax1x.com/2019/06/05/VNGTdP.jpg">
                <img src="https://s2.ax1x.com/2019/06/05/VNG5qI.jpg">
            </div>
            <span class="arrow left-arrow">&lt;</span>
            <span class="arrow right-arrow">&gt;</span>
            <div id="dots">
                <!-- 使用小点标记实际多少张图片,要添加图片时需要修改carousel_images和此处 -->
                <span class="dot active"></span>
                <span class="dot"></span>
                <span class="dot"></span>
            </div>
        </div>
        <div id="test">
            <h1>blog</h1>
            <h1>blogs</h1>
            <h1>blog</h1>
            <h1>blog</h1>
        </div>
    </div>
    <!-- END 轮播图 -->
</body>
</html>

{% endfold %}

CSS

{% fold %}

body {
	overflow: scroll;
}

/* 轮播图样式表 */

#content #carousel_wrap {
	position: relative;
	margin: 0 auto;
	width: 100%; /* 轮播图宽度 */
	overflow: hidden;
}

#content #carousel_wrap #carousel_images {
	position: absolute;
	border: 0;
	outline: none;
	white-space: nowrap; /* 将图片一行排列 */
	width: 100%;
	font-size: 0; /* 清除white-space间隙 */
	margin: 0px; 
}

#content #carousel_wrap #carousel_images img {
	width: 100%;
}

#content #carousel_wrap .arrow {
	position: absolute;
	font-weight: bold;
	font-size: 50px;
	color: lightgray;
	top: 50%;
	transform: translateY(-50%);
	cursor: pointer;
	transition-property: opacity;
	transition-duration: 0.5s;
}

#content #carousel_wrap .arrow:hover {
	opacity: 0.5;
}

#content #carousel_wrap .left-arrow {
	left: 20px;
}

#content #carousel_wrap .right-arrow {
	right: 20px;
}

#content #carousel_wrap #dots {
	position: absolute;
	bottom: 20px;
	left: 50%;
	transform: translateX(-50%)
}

#content #carousel_wrap .dot {
	background-color: white;
	display: inline-block;
	width: 10px;
	height: 10px;
	border-radius: 50%;
	margin: 4px;
	opacity: 0.2;
	cursor: pointer;
}

#content #carousel_wrap .active {
	opacity: 1;
}

.transition {
	transition-property: left;
	transition-duration: 1s;
}
/* END 轮播图样式表 */

{% endfold %}

JS

{% fold %}

window.onload = function() {
	var carouImg = document.getElementById("carousel_images");
	var carouWrap = document.getElementById("carousel_wrap");
	var img = carouImg.getElementsByTagName("img")[0];
	var leftArrow = document.getElementsByClassName("left-arrow")[0];
	var rightArrow = document.getElementsByClassName("right-arrow")[0];
	var oBtn = document.getElementsByClassName("dot");
	var index = 0;
	var index_length = oBtn.length;

	// 给图片添加过渡效果
	carouImg.classList.add("transition");

	// 动态获取绝对定位轮播图的高度,设置carousel_wrap的高度,宽度为整个main宽度
	// 如果mystyle.css中使用overflow:auto->含有滚动条宽度; 故使用overflow:scroll
	carouImg.style.left = -img.clientWidth + "px"; 
	console.log(carouImg.style.left);
	carouWrap.style.height = img.offsetHeight + "px";

	// 监听body大小变化,修改轮播图的图片位置和高度
	document.body.onresize = function () { 
		carouImg.style.left = -img.clientWidth + "px";
		carouWrap.style.height = img.offsetHeight + "px";
	}
	
	// 点击右箭头
	rightArrow.onclick = function() {
		next_pic();
		showCurrentDot(index);
	}

	// 点击左箭头
	leftArrow.onclick = function () {
		pre_pic();
		showCurrentDot(index);
	}
	
	// 点击小点
	for (let i = 0; i < oBtn.length; ++ i) {
		oBtn[i].onclick = function() {
			var newLeft = (-img.clientWidth) * (i + 1);
			carouImg.style.left = newLeft + 'px';
			console.log(i);
			showCurrentDot(i);
		}
	}

	// 下一张图片
	function next_pic() {
		var left = parseInt(carouImg.style.left);
		if (left <= (-img.clientWidth) * (index_length + 1)) {
			// 临界情况判断
			carouImg.classList.remove("transition");
			var newLeft = -img.clientWidth * 1;
			carouImg.style.left = newLeft + 'px';
			newLeft = -img.clientWidth * 2;
			carouImg.classList.add("transition");
			index = 1;
		}
		else {
			// 一般情况
			var newLeft = parseInt(carouImg.style.left) - img.clientWidth;
			(index == (index_length - 1)) ? index = 0 : index += 1;
		}
		carouImg.style.left = newLeft + 'px'; // 不要忘记添加'px'
		console.log(newLeft);
	}

	// 上一张图片
	function pre_pic() {
		var left = parseInt(carouImg.style.left);
		if (left >= -10) {
			// 临界情况判断
			carouImg.classList.remove("transition");
			var newLeft = -img.clientWidth * index_length;
			carouImg.style.left = newLeft + 'px';
			newLeft = -img.clientWidth * (index_length - 1);
			carouImg.classList.add("transition");
			index = index_length - 2;
		}
		else {
			// 一般情况
			var newLeft = parseInt(carouImg.style.left) + img.clientWidth;
			(index == 0) ? index = (index_length - 1) : index -= 1;
		}
		carouImg.style.left = newLeft + 'px';
		console.log(newLeft);
	}

	function showCurrentDot(index) {
		for (let i = 0; i < oBtn.length; ++ i) {
			(i == index) ? oBtn[i].classList.add("active") : oBtn[i].classList.remove("active");
		}
	}

	// 设置轮播定时器
	var timer = setInterval(function(){
		next_pic();
		showCurrentDot(index);
	}, 3000);

	carouWrap.onmouseover = function() {
		clearInterval(timer);
	}

	carouWrap.onmouseout = function() {
		timer = setInterval(function () {
			next_pic();
			showCurrentDot(index);
		}, 3000);
	}
}

{% endfold %}


思考和总结

总的来说,这一次的轮播图实现,自己还是比较满意的,从代码的优化通用性和逻辑的清晰度来说,都比之前写的那次轮播图好多了,更不用说这一次的轮播图的实现难度比上次的要大,这次的轮播要求是响应式而不是定长的,这么想想自己还是挺高兴的。

不过也有不足,本以为是“完美”的轮播了,但是还是有十分小的bug:进入第一张和最后一张图片的补充替补图片时,点击提示点,过渡是按照替补图片而不是正式图片过渡的,这里是不足的地方。

寻思着可以通过index来区分正式和替补图片,然后消除过渡,把正式图片和替补图片互换,再开启过渡效果,这样就可以解决这个小bug了。

不过最近时间很紧,小作业大作业好多,日后有机会再修改成“完美”的代码。


更新

更新:解决了上面提到的bug,思路就是总结里提到的,在关键地方清除过渡,修改图片位置,再添加过渡。

具体做法:在点击提示点的代码中修改。

注意click事件里left值修改完毕再添加transition过渡,如果只是修改到实际图片位置,但是没有修改本次点击图片位置就添加transition,bug依然存在,这里没有想明白,难道是因为click事件点击和松开鼠标这个过程的某种原理吗?

for (let i = 0; i < oBtn.length; ++ i) {
		oBtn[i].onclick = function() {
			var left = parseInt(carouImg.style.left);
			var newLeft;

			// 如果没有临界判断,当图片位于“替补图片”时,点击提示点会有错乱过渡
			if (left <= (-img.clientWidth) * (index_length + 1)) {
				// 临界情况判断
				carouImg.classList.remove("transition");
				newLeft = -img.clientWidth * 1;
				carouImg.style.left = newLeft + 'px';
			}
			if (left >= -10) {
				// 临界情况判断
				carouImg.classList.remove("transition");
				newLeft = -img.clientWidth * index_length;
				carouImg.style.left = newLeft + 'px';
			}
			
			newLeft = (-img.clientWidth) * (i + 1);
			carouImg.style.left = newLeft + 'px';
			// 注意click事件的执行过程,要在修改完left后添加transition类
			carouImg.classList.add("transition");
			index = i;
			showCurrentDot(i);
		}
	}
初学前端,记录学习的内容和进度~
原文地址:https://www.cnblogs.com/xiuhua/p/13398778.html