幻灯片的代码可以很简单,也可以很复杂,普通的上一张下一张的幻灯片很容易做,但是最后一张要切换到第一张的时候,大部分都是从最后一张拉回到第一张,中间有长长的滑动,而要实现无缝循环播放,貌似最后一张后面又是第一张,好像无穷尽的循环一样,则需要更复杂的代码。
先看具体的效果:
具体的实现思路:
1、首先复制第一张图片到最后一张,把最后一张图片复制到第一张。所以如果有5张图片,实际有7张图片。
2、滑动到了最后一张图片,立刻把位置切换到第二张图片,切换的时候清除过渡动画。
3、滑动到了第一张图片,立刻把位置切换到倒数第二张图片,切换的时候清除过渡动画。
4、关键点是,需要等待滑动动画结束之后,才切换,而切换的时候不能有动画,切换结束后,下一次的滑动又有动画。
5、自动播放,鼠标悬停停止自动播放,点击上一张下一张和小圆点都可以滑动。
关键代码如下:
一、HTML代码
<div class="carousel-wrapper"> <!--轮播图--> <ul class="carousel-item-wrapper clearfix"> <li><a href="#"><img src="images/1.jpg" alt=""></a></li> <li><a href="#"><img src="images/2.jpg" alt=""></a></li> <li><a href="#"><img src="images/3.jpg" alt=""></a></li> <li><a href="#"><img src="images/4.jpg" alt=""></a></li> <li><a href="#"><img src="images/5.jpg" alt=""></a></li> </ul> <!-- 小圆点 --> <ul class="carousel-index-wrapper"> <!--根据图片数量生成--> <!-- <li class="carousel-index-btn active-carousel-index-btn" id="carousel-to-1"></li> <li class="carousel-index-btn" id="carousel-to-2"></li> --> </ul> <!--左右按钮--> <a href="javascript:;" id="prev"><</a> <a href="javascript:;" id="next">></a> </div>
二、主要的js代码
//图片数据准备阶段 var oSlideWrap = document.querySelector(".carousel-wrapper"); var oSlideUl = document.querySelector(".carousel-item-wrapper"); // 动态更新的方法 var oSlideLi = oSlideUl.getElementsByTagName("li"); var oDots = document.querySelector(".carousel-index-wrapper"); var oDLi = oDots.getElementsByTagName("li"); var oPrev = document.querySelector("#prev"); var oNext = document.querySelector("#next"); // 在更新li之前先把第一个li和最后一个li保存起来,避免更新。 var firstLi = oSlideLi[0]; var lastLi = oSlideLi[oSlideLi.length - 1]; // 根据图片个数生成小圆点 for (var i = 0; i < oSlideLi.length; i++) { oDots.innerHTML += '<li class="carousel-index-btn"></li>' } // 初始化当前小圆点的高亮状态 oDLi[0].className += " active-carousel-index-btn"; //克隆第一个li,加入到ul中的最后 oSlideUl.appendChild(firstLi.cloneNode(true)); // 克隆最后一个li加入到ul的最前面 oSlideUl.insertBefore(lastLi.cloneNode(true), firstLi); // 更新之后的li的长度。 var len = oSlideLi.length; // 计算出li的长度 var liWidth = oSlideWrap.offsetWidth; // 计算出整个ul的长度 oSlideUl.style.width = liWidth * len + "px"; // 初始化在第二张图片位置上 oSlideUl.style.left = -liWidth + "px"; //给文档加载完成后的load事件绑定相应的处理函数: addLoadEvent(preventDefaultAnchors); addLoadEvent(carouselControl); //便于拓展的方法一 function addLoadEvent(func) { var oldLoad = window.onload; if (typeof oldLoad != 'function') { window.onload = func; } else { window.onload = function () { oldLoad(); func(); } } } /*用一个对象把轮播组件的相关参数封装起来,优点是便于扩展升;缺点是同时也增加了文件的体积。*/ var carouselInfo = { // li的宽度 itemWidth: liWidth, // 图片的数量 trueItemNum: len - 2, // 复制之后的图片数量 itemNum: len, // ul的总宽度 totalWidth: len * liWidth }; function preventDefaultAnchors() { //阻止a标签默认的点击跳转行为 var allAnchors = document.querySelectorAll('a'); for (var i = 0; i < allAnchors.length; i++) { allAnchors[i].addEventListener('click', function (e) { e.preventDefault(); }, false); } /*火狐和IE不支持NodeList的forEach方法*/ } // 页面加载完毕执行幻灯函数 // 主要包括两个函数,一个幻灯切换,一个小圆点切换。 function carouselControl() { var prev = document.querySelector("#prev"); var next = document.querySelector("#next"); var carouselWrapper = document.querySelector(".carousel-wrapper"); var indexBtns = document.querySelectorAll(".carousel-index-btn"); var currentItemNum = 1; //标记当前所在的图片编号,用于配合index btn。 // 上一张下一张按钮点击执行幻灯切换,返回当前图片的索引值 prev.onclick = function () { currentItemNum = prevItem(currentItemNum); }; next.onclick = function () { currentItemNum = nextItem(currentItemNum); }; // 小圆点切换,点击小圆点切换图片,图片比小圆点的下标值多一个1 for (var i = 0; i < indexBtns.length; i++) { //利用立即调用函数,解决闭包的副作用,传入相应的index值 (function (i) { indexBtns[i].onclick = function () { slideTo(i + 1); currentItemNum = i + 1; } })(i); } // 定时器播放 var scrollTimer = null; // 初始化自动播放函数 play(); // 自动播放 function play() { // 定时器开始之前要先结束 clearInterval(scrollTimer); scrollTimer = setInterval(function () { currentItemNum = nextItem(currentItemNum); }, 2000); } // 结束自动播放 function stop() { clearInterval(scrollTimer); } // 鼠标经过取消定时播放,鼠标移出恢复定时播放 carouselWrapper.addEventListener('mouseover', stop); carouselWrapper.addEventListener('mouseout', play, false); /*DOM二级的addEventListener相对于on+sth的优点是: * 1.addEventListener可以先后添加多个事件,同时这些事件还不会相互覆盖。 * 2.addEventListener可以控制事件触发阶段,通过第三个可选的useCapture参数选择冒泡还是捕获。 * 3.addEventListener对任何DOM元素都有效,而不仅仅是HTML元素。*/ } // 下一张函数,图片索引值加1,图片滑动,1为正值,表示下一张滑动 function nextItem(currentItemNum) { slide(1); currentItemNum += 1; // 到了最后一张,回到第二张 if (currentItemNum == len - 1) currentItemNum = 1; switchIndexBtn(currentItemNum); return currentItemNum; } // 上一张函数,-1表示上一张滑动 function prevItem(currentItemNum) { slide(-1); currentItemNum -= 1; // 到了第一张,回到倒数第二张 if (currentItemNum == 0) currentItemNum = len - 2; switchIndexBtn(currentItemNum); return currentItemNum; } // 图片是往下还是往上滑动,以及到了临界点如何切换。 function slide(slideItemNum) { var itemWrapper = document.querySelector(".carousel-item-wrapper"); // 获取当前ul的left值 var currentLeftOffset = (itemWrapper.style.left) ? parseInt(itemWrapper.style.left) : -carouselInfo .itemWidth, // 如果为-1,上一张,为1,下一张,计算出目标图片的位置 targetLeftOffset = currentLeftOffset - (slideItemNum * carouselInfo.itemWidth); switch (true) { /*switch 的语法是:当case之中的表达式等于switch (val)的val时,执行后面的statement(语句)。*/ // 到了第一张图片,再点击目标位置就是928,满足条件 case (targetLeftOffset > 0): itemWrapper.style.transition = "none"; // 当播放第一张图片的时候,把ul拉到倒数第二张。这个时候没有动画。 /*此处即相当于:itemWrapper.style.left='-928*(len-2)px';*/ itemWrapper.style.left = -carouselInfo.trueItemNum * carouselInfo.itemWidth + 'px'; // 上一张目标图片的left值,为-928*(len-3)) targetLeftOffset = -(carouselInfo.trueItemNum - 1) * carouselInfo.itemWidth; break; // 到了最后一张图片,再点击一下目标位置就是-928*len,则符合该条件 case (targetLeftOffset < -(carouselInfo.totalWidth - carouselInfo.itemWidth)): //此处即相当于:targetLeftOffset<-928*(len-1) itemWrapper.style.transition = "none"; // itemWrapper.style.left='-928px'; itemWrapper.style.left = -carouselInfo.itemWidth + 'px'; // 下一张目标图片的left:-928*2 targetLeftOffset = -carouselInfo.itemWidth * 2; break; } // 点击了按钮后,执行滑动到目标位置,如果到了临界点,则会先执行上面的switch,再执行该滑动行为。 setTimeout(function () { itemWrapper.style.transition = "left .2s ease-in"; itemWrapper.style.left = targetLeftOffset + 'px'; }, 20); /* * 具体到这个轮播,就是在上一轮非过渡定位的页面渲染工作(switch语句内部的case)结束之后,再执行setTimeout内部的过渡位移工作。 * 从而避免了,非过渡的定位还未结束,就恢复了过渡属性,使得这一次非过渡的定位也带有过渡效果。*/ /*偶尔有几次观察到即使用了setTimeout-0也会出现之前的bug:......,而且难以重现和观测。 * 所以试着加上了20ms的延迟,测试还会不会出现这个bug,从而进一步判断原因。 * * 加上了20ms延迟之后,没有再看到过这个bug。*/ /* 当我们写为 setTimeout(fn,0) 的时候,实际是实现插队操作,要求浏览器“尽可能快”的进行回调,但是实际能多快就完全取决于浏览器了。所以这个0的作用就是他会改变任务的执行顺序。因为浏览器会先执行当前队列里面的任务再去执行setTimeout的内容。 */ } // 点击小圆点图片的切换和小圆点高亮的切换 function slideTo(targetNum) { var itemWrapper = document.querySelector(".carousel-item-wrapper"); itemWrapper.style.left = -(targetNum * carouselInfo.itemWidth) + 'px'; switchIndexBtn(targetNum); } // 小圆点高亮的切换 function switchIndexBtn(targetNum) { //delete the past active-index class var activeBtn = document.querySelector(".active-carousel-index-btn"); activeBtn.className = activeBtn.className.replace(" active-carousel-index-btn", ""); //add a new active btn var targetBtn = document.querySelectorAll(".carousel-index-btn")[targetNum - 1]; targetBtn.className += " active-carousel-index-btn"; }
完整的代码可以在百度云盘下载。
下载地址:
[$]链接: https://pan.baidu.com/s/1a4hJ6-NsNu9pHbcURSixcA 提取码: n9vw [/$]
今天看到一个直播视频里是利用重绘offsetLeft来解决到了临界点取消过渡动画效果的。
大概思路就是:
ul的left值小于等于临界点
取消transition的效果
重新把left的值定为0,
再重新通过offsetLeft得到left的值,相当于重绘页面,不然取消transition和添加transition挨得太近会组合在一起相互抵消了。
再得到新的目标值,就是左移一张图片
加上transition过渡效果,
再把新的目标值赋给ul的left,就可以实现在临界点立刻回到起始点而没有过渡效果。不知道这个方法比起这里的setTimeout的方法,哪个性能更好?
发表评论:
◎请发表你卖萌撒娇或一针见血的评论,严禁小广告。