幻灯片的代码可以很简单,也可以很复杂,普通的上一张下一张的幻灯片很容易做,但是最后一张要切换到第一张的时候,大部分都是从最后一张拉回到第一张,中间有长长的滑动,而要实现无缝循环播放,貌似最后一张后面又是第一张,好像无穷尽的循环一样,则需要更复杂的代码。
先看具体的效果:
具体的实现思路:
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的方法,哪个性能更好?

发表评论:
◎请发表你卖萌撒娇或一针见血的评论,严禁小广告。