首页>案例集>JavaScript案例集

js原生代码实现无缝滚动循环播放幻灯片

幻灯片的代码可以很简单,也可以很复杂,普通的上一张下一张的幻灯片很容易做,但是最后一张要切换到第一张的时候,大部分都是从最后一张拉回到第一张,中间有长长的滑动,而要实现无缝循环播放,貌似最后一张后面又是第一张,好像无穷尽的循环一样,则需要更复杂的代码。

先看具体的效果:

banner.jpg点击图片可以查看效果。

具体的实现思路:

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">&lt;</a>
        <a href="javascript:;" id="next">&gt;</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的方法,哪个性能更好?


点赞


3
保存到:

相关文章

发表评论:

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

Top