无缝轮播图是页面常用的特效之一,然而在实际的开发过程中,大部分的开发者都会使用插件来对轮播图进行开发,那么它的底层到底是怎么实现的呢,本文章将围绕这一问题展开探讨。
在讨论如何利用原生JS实现图片之间无缝切换轮播的动画效果之前,我们先来谈谈无缝轮播图片的css布局。
首先我们需要一个用来显示图片的DIV容器,然后把想要轮播的图片没有缝隙的排成一行放入DIV容器中,给DIV容器设置 overflow: hidden,这样在页面中就可以只看到一张图片,然后通过利用JS来移动ul的left值就能达到无缝轮播的动画效果。
然而这还不够,我们还需要在第一张图片前放最后一张图片,以及在最后一张图片后放第一张图片,这样做得目的是为了实现第一张图片和最后一张图片切换时能达到无缝的动画效果。核心代码和布局效果如下:
<div id="box"> <ul> <li><img src="img/5.jpg"></li> <li><img src="img/1.jpg"></li> <li><img src="img/2.jpg"></li> <li><img src="img/3.jpg"></li> <li><img src="img/4.jpg"></li> <li><img src="img/5.jpg"></li> <li><img src="img/1.jpg"></li> </ul> </div>
下图是给DIV容器设置overflow: hidden前的效果:
布局搞定之后,接下来就是如何利用原生JS实现无缝轮播。
首先我们需要两个核心的函数,一个用于实现图片无缝切换时的减速运动,另一个用于获取ul的left值(注意:无缝轮播每次移动的都是整个ul的left值)
function fnMove(ele, obj,callback) { //参数一:需要动态变化样式的元素 //参数二:一个对象 其 键为需要变化的css属性名,值为css属性目标值 例{fontSize:500,height:140,opacity:50} //参数三:回调如果动画结束 调用该函数 clearInterval(ele.timer); //创建计时器动态的修改 目标元素的css属性值 // 用ele存储timer 目的不让计时器number被释放,导致无法阻止计时器 ele.timer = setInterval(function () { var fnStop = true; //标记 判断是否所有动画都达到目标值 for (var attr in obj) { //遍历对象 获取到所有需要修改的属性名和目标值 var curr = 0; // 元素当前css属性值 如果属性是opacity 0 - 1 // 因为我们的速度大于等于1 直接操作0-1小数不太好操作 // 我们对他进行放大100倍操作 if(attr == ‘opacity‘){ curr = parseInt(getStyle(ele, attr)*100) }else { curr = parseInt(getStyle(ele, attr)) } //定义一个速度 让目标元素 每30毫秒增加5像素 直到达到目标值停止计时器(动画结束) //减速运动 目标值(不变) - 减去当前值(越来越接近目标) 减出来的结果越来越小 除以6不要让当前值一瞬间达到目标值 var speed = (obj[attr] - curr)/6; //放大动作 0.3 => 1 -0.3 => -1 避免让速度等于0 speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed); if(speed != 0){ fnStop = false; } // console.log(curr); //动态改变css属性值 如果是透明度 我们之前给他乘以100倍 这里需要还原回0-1的数 if(attr == ‘opacity‘){ ele.style[attr] = parseFloat((curr + speed)/100); //透明度兼容写法 老版本浏览器透明度为0-100 ele.style.filter = ‘alpha(opacity:‘+(curr + speed)+‘)‘ }else { ele.style[attr] = curr + speed + ‘px‘ } } //所有动画完成,调用回调函数(可选) if (fnStop){ clearInterval(ele.timer); if(callback){ callback(); } } }, 30)
function getStyle(ele, sAttr) { //参数一:需要获取的元素对象 //参数二:需要获取的样式属性 //兼容写法获取 元素css样式的值 //高版本浏览器getComputedStyle(元素,伪类),不是伪类设为null,返回的是一个CSS样式声明对象 if (window.getComputedStyle) { sAttr = window.getComputedStyle(ele, null)[sAttr]; } else { //低版本浏览器 sAttr = ele.currentStyle(sAttr); } return sAttr }
有了这两个方法,实现无缝轮播将变得简单。
首先我们需要一个定时器,让图片每两秒进行一次轮播。其次,我们还需要一个用于移动图片的函数,把该函数放入定时器中,每两秒调用一次,实现轮播效果。
//通过js获取图片/li的宽度,得到每次轮播移动多少像素 var imgWidth = liList[0].offsetWidth; //定义当前轮播图的下标(用来记录下一次该展示第几张图片) var index = 1; // 图片从第一张开始 index==0 其实是最后一张图片 //页面小圆点高亮的下标 var num = 0; //开始进行轮播 box.timer = setInterval(showRight, 2000);
function showRight() { index++; num++; if (num >= spans.length) { //spans为与展示图片数量相同的小点数组 num = 0; } if (index >= liList.length) { //上一次已经展示了第一张(li最后一张)了,该展示第二张 // 直接设置css属性left 是没有动画效果的 无缝的让最后一张跳回 第1张 ul.style.left = -imgWidth + ‘px‘; //改变index值 动画效果的让第一张移动到第二张 index = 2; } fnMove(ul, {left: -imgWidth * index}) activeSpan(num); //点亮与图片相对应的小圆点 }
//高亮小圆点方法 function activeSpan(num) { for (var i = 0; i < spans.length; i++) { spans[i].className = ‘‘; } spans[num].className = ‘bgcolor‘ }
效果图:
接下来给左右按钮绑定点击事件,右按钮每次点击就是调用一次showRight()函数,有了showRight()函数,showLeft()函数也很容易编写出来(注意:每次点击需要清除原来进行轮播的定时器):
left.onclick = function (e) { //阻止事件冒泡的兼容写法 e = e || window.event; window.event ? e.cancelBubble = true : e.stopPropagation(); clearInterval(box.timer); showLeft(); } right.onclick = function (e) { e = e || window.event; window.event ? e.cancelBubble = true : e.stopPropagation(); clearInterval(box.timer); showRight(); }
效果图(实际有鼠标在对左右按钮进行点击):
最后利用for循环给每个小圆点绑定点击事件,这里涉及到JS一个特性:异步事件队列。
在这里简单解释一下:页面加载时是从上往下地对代码进行同步加载,当遇到需要事件触发的回调函数时,JS会把该回调函数丢入异步事件队列当中,然后继续往下加载代码。当页面中有事件被触发时,JS会从异步事件队列中调用与该事件绑定的所有回调函数。
所以,在利用for循环给每个小圆点绑定点击事件时,我们需要记录下每个小圆点的下标,在调用与点击小圆点事件绑定的回调函数中读取与小圆点对应的下标。
//给小圆点添加点击 function spansClick() { for (var i = 0; i < spans.length; i++) { //记录每个小圆点的下标 spans[i].index=i; spans[i].onclick = function (e) { e = e || window.event; window.event ? e.cancelBubble = true : e.stopPropagation(); //读取每个小圆点的下标 num = this.index; index = num + 1; //index永远比num大1 activeSpan(num); fnMove(ul, {left: -imgWidth * index}) } } }
效果图(实际有鼠标对小圆点进行点击)