js实现滑动轮播图

3,190 阅读6分钟

什么是滑动轮播图

轮播图即将一组图片在特定的盒子中定时顺序播放,下方存在一组小圆点显示当前播放顺序即第几张,按需出现左右按钮控制手动切换到上/下一张图片。

滑动轮播图原理

将一组图片放在一行中平铺,通过计算偏移量实现定时轮播的效果; 轮播图示意图.png
上图以三张图片一组为例,三张图片水平排列,按照箭头所示方向定时向左移动,红色方框即我们所能看到的轮播图的范围(图片中为避免边界线重合看不清楚,红色方框比1号方框略大,实际设计中一般轮播图盒子和图片大小一致),这样看起来就像是图片定时播放的效果,这种就是滑动轮播图,demo效果图如下: 2.gif

代码思路

页面设计

首先,我们需要一个盒子(盒子大小应与单张图片大小一致)放这组图片;
其次,在盒子的中下部,我们需要放和图片张数相等的小圆点,显示当前整在播放第几张图片;
最后,在图片盒子的左右边缘,我们需要放两个按钮,用来点击上/下一张效果。 HTML代码如下:

<div class="carousel">
    <ul id="pics">
        <!-- 头部插入一张图片保证首尾平滑过渡,头部插入是为了第一轮点击左划时平滑过渡-->
        <!-- 尾部插入一张图片保证首尾平滑过渡,尾部插入是为了第一轮结束时顺利到第二轮-->
        <li><img src="images2/carousel7.jpg"></li>
        <li><img src="images2/carousel1.jpg"></li>
        <li><img src="images2/carousel2.jpg"></li>
        <li><img src="images2/carousel3.jpg"></li>
        <li><img src="images2/carousel4.jpg"></li>
        <li><img src="images2/carousel5.jpg"></li>
        <li><img src="images2/carousel6.jpg"></li>
        <li><img src="images2/carousel7.jpg"></li>
        <li><img src="images2/carousel1.jpg"></li>
    </ul>
    <ul id="dots">
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
    <div class="move_btns">
        <div class="leftbtn" onclick="move_prior()">
            <div class="move_l"></div>
        </div>
        <div class="rightbtn" onclick="move_next()">
            <div class="move_r"></div>
        </div>
    </div>
</div>

滑动轮播的首尾处理

demo中使用的是七张图片,为了保证尾部平滑过渡,我们需要在图片组的尾后加上第一张图片(后文称为第八张),在轮播图播到第七张图片时,正常播放下一张即第八张,此时我们需要迅速地将整个图片组向后平移到第一张图片顶图片盒子左侧的位置,因为第一张和第八张是相同的图片,所以我们肉眼看不到变化。
为保证头部平滑过渡,同理在第一张图片之前插入第七张图片。

js代码思路

首先,设置index控制当前播放的图片次序及其他公共变量

    let ul = document.getElementById("pics");
    let pics = ul.querySelectorAll("li");  //获取轮播图板块中的图片信息
    let dots = document.getElementById("dots").querySelectorAll("li");  //获取小圆点信息

    let index = 0;  //图片初始下标值为0
    let timer = null; //自动轮播定时器
    let manual_timer = null;  //手动轮播定时器,判断边界图片偏移量跳转
    let op = "auto"; //操作方式为自动轮播或手动轮播标志位

自动轮播函数

function move() { //自动轮播函数
        console.log(index + "NO的动画效果" + ul.style.transition)
        ul.style.left = -900 * (index + 1) + "px";
        if (index == -1) {
            index = 6;
            setTimeout(() => {
                ul.style.left = -900 * (index + 1) + "px";
                ul.style.transition = "none";
            }, 1000)
        }
        //不能用for循环移动ul,应该是指定时间内移动整个ul,循环li没有意义
        judge_back();
        //根据index决定小圆点背景色
        // console.log("调用激活圆点函数传入index=" + index);
        activeDot(index);  //该行代码不能放在if前,否则第二轮循环时第一二张图片切换小圆点的切换快于图片
    }
function autoMovePics() {
        timer = setInterval(() => {
            move();
            index++;  //该句放在move函数前,保证第二轮的一二张图片的下一张跳转顺畅
            // console.log("自动轮播后index=" + index);
        }, 3000);
    }

判断是否需要迅速退回函数代码

function judge_back() {  //判断是否需要迅速退回函数
        if (index === pics.length - 2) {
            setTimeout(() => {
                index = op == "auto" ? 0 : 1;  //若为自动播放改为0,否则为1
                //若为按钮轮播,则第一轮的最后一张应直接跳到第二轮的第二张,因为第一轮的最后一张与第二轮的第一张相同
                ul.style.left = "-" + 900 * (index + 1) + "px";
                ul.style.transition = "none";
                // console.log("迅速退回原位");
                // 这里要直接设置过渡css样式,保证迅速回到原位
            }, 1000)
        } else
            ul.style.transition = "all 1s ease";
    }

激活对应小圆点代码

//函数激活小圆点,即将对应小圆点透明度消除
    function activeDot(active_index) {
        //该参数index实际比NodeList对应下标大1
        active_index = active_index >= dots.length ? (active_index - dots.length) : active_index;  //index大于长度时恰是整个ul一次循环结束后
        // console.log("三元表达式之后的active_index=" + active_index);
        dots[active_index].style.opacity = "1";
        dots[active_index].style.transition = "all 1s ease";
        for (let i = 0; i < dots.length; i++) {
            if (active_index != i) {
                dots[i].style.opacity = ".5";
                dots[i].style.transition = "none";
            }
        }
    }

上一张/下一张按钮点击函数

// 向前一张图片移动
    function move_prior() {
        clearMove();
        op = "manual";
        index--;
        judge_back();
        move();
        op = "auto";
        autoMovePics();
    }

    // 向后一张图片移动
    function move_next() {
        clearMove();  //清除定时器,定时器为异步函数,不清楚的话将始终在执行
        op = "manual";
        index++;
        judge_back();
        move();
        op = "auto";  //手动操作结束后变更标志位
        autoMovePics();
    }

需要下载代码的小伙伴请点击github_carousel

问题总结

开发中遇到的细微问题


1. 弹性布局控件left属性
使用了弹性布局的ul,需要加上relative的定位才能使ul上的left属性生效
2. getElementsByClassName取不到元素
getElementsByClassName取到的元素设置style属性报错未定义,需使用getElementsById
3. function命名
不要和clear等关键字重名,否则在按钮的onclick事件中不会调用
4. 溢出部分隐藏 overflow
overflow:hidden应该是放在父盒子上的,比如父盒子里有一个ul,ul溢出父盒子部分需隐藏,那么overflow这行代码自然应该放在父盒子而非ul的样式里,若放在ul样式里代表li溢出ul部分隐藏
5. 清除定时器
按钮播放必须清除定时器
6. 静态querySelectAll()
文档结构的改变,不会影响到NodeList,但会影响到HTMLCollection对象,HTMLCollection对象会动态改变
7. 三元表达式不能写成index++
index = index == dots.length ? 1 : ++index; //此处三元表达式不能写成index++,后自增是先返回值后自增1,index值实际上不会改变
8. activeDot()函数不能更改index值,后面需要用该值做判断
9. 图片的ul默认left值设为0
left默认值不设为0,第一轮的一二张图片手动切换会闪现
10. setTimeOut transition动画存在执行时间
11. top属性
设置成百分比是相对于父元素来计算的,要当心div>ul>li>img的情况,在li上设置top值是根据ul的高度计算而不是div

开发中遇到的疑难问题

在判断是否需要迅速退回函数中,我原本的代码是没有setTimeout的,此时会出现一个问题:当轮播图播放到第八张图片时,此时本应该迅速右移到第一张图片顶图片盒子左侧的位置,但即使我将transition设为none,我们依然可以看到整个图片组的平移过程,显然这不是我们要的效果。
这个问题出现的原因是transition本身就有执行时间,我们经常将transition中的属性简写,而transition中的时间属性transition-delay指的是过渡延迟时间,即使立即执行过渡依然存在本身的执行时间。
解决方案:当图片轮播到第七张时,添加setTimeOut,设置其一秒(轮播过渡效果的延迟时间是一秒)后执行回退动画,一秒后正好播放到第八张,此时回退肉眼是看不到效果的。或者不用transition,自己封装动画函数。
小结:因为transition本身的执行时间,我们不能在轮播到第八张的时候执行回退,而应该在第七张时开始设置一秒后的定时器让其回退。