zoukankan      html  css  js  c++  java
  • 【JS】轮播图

    前言

    轮播图在前端中的应用场景非常多、应用频率非常高,大到网站商城,小到个人主页,都会有用到轮播图的时候。

    现在网上的轮播图插件也非常多,花样花式也各异,有基于jq的、基于框架,所以一般是不用我们去手写轮播图的。

    但在某些情况下,我们还是需要去手写轮播图。

    手写原生js轮播图,有助于我们知道轮播图的实现原理。

    知道了原理,有时候我们也能根据自己的需求去修改下载下来插件的源码。

    效果

    我们先来看下轮播图的最终效果图:

    根据上面的效果图,我们来总结一下我们要实现的轮播图的功能:

    • 箭头点击
    • 按钮点击
    • 滑动过渡
    • 自动播放

    原理

    轮播图的原理要配合html结构和css样式来讲。

    我们先来看一下html结构:

    <div class="slide-wrapper" id="slide_wrapper">
        <div class="slide-imgs" id="slide_imgs" style="left: -400px;">
            <img class="slide-img" src="slide-img3.jpg"><!-- 辅助过渡图 -->
            <img class="slide-img" src="slide-img0.jpg">
            <img class="slide-img" src="slide-img1.jpg">
            <img class="slide-img" src="slide-img2.jpg">
            <img class="slide-img" src="slide-img3.jpg">
            <img class="slide-img" src="slide-img0.jpg"><!-- 辅助过渡图 -->
        </div>
        <div class="slide-btns" id="slide_btns">
            <span class="slide-btn active" data-index="0"></span>
            <span class="slide-btn" data-index="1"></span>
            <span class="slide-btn" data-index="2"></span>
            <span class="slide-btn" data-index="3"></span>
        </div>
        <a class="slide-arrow" id="slide_left" href="#">&lt;</a>
        <a class="slide-arrow" id="slide_right" href="#">&gt;</a>
    </div>

    上图是我们整个轮播图的html结构,可以看出代码是很少的。

    可以看到,我实现的轮播图用了4张图片,但代码中有6张,我用代码注释标记的2张图片是辅助图,用来辅助动画过渡,下面会讲到。

    请注意观察 slide-wrapper 和 slide-imgs 。

    另外可以发现 class="slide-imgs" 元素上有样式属性 left ,在html里写样式是为了方便我们待会在js中取该元素的 left 的值。

    接下来我们配合在浏览器里的效果(按F12打开控制台)来讲解轮播图的原理:

    从图片中可以看出,我们轮播图每张图片的宽度是400px,同样的,整个轮播图容器 .slide-wrapper 的宽度也是 400px,当然高度也是一样。

     

    上面的图片是 .slide-imgs 元素,它是包裹我们所有图片的容器,它的宽度是2400px,这是因为我们有6张图片,每张图片是400px(上图因为太长我没有截出)。

    接着就是设置 .slide-wrapper 元素的样式,将轮播图最外层的包裹元素 overflow: hidden; :

    .slide-wrapper {
        position: relative;
        width: 400px;
        height: 250px;
        overflow: hidden;
    }

    轮播图的原理就是让“包裹图片的容器元素”(.slide-imgs元素)做位置偏移

    每次偏移的距离是每张图片的宽度。

    每次切换的时候,我们就重新改变 .slide-imgs元素 的样式属性 left 的值(将.slide-wrapper设置为相对定位,将.slide-imgs设置为绝对定位)。

    从上图就可以看到所有图片的包裹元素进行了位置偏移,这就是轮播图的核心原理了。

    箭头点击

    var slide_left = document.getElementById('slide_left'),
        slide_right = document.getElementById('slide_right'),
        slide_imgs = document.getElementById('slide_imgs'),
    slide_left.onclick = function() {
        ...
    
        imgChange(400);
    
        ...
    };
    
    slide_right.onclick = function() {
        ...
    
        imgChange(-400);
    
        ...
    };

    首先获取我们两个箭头按钮的元素,编写点击后要执行的代码,

    每一次点击,就切换一张图片,切换的大小是图片的宽度,这里是400px。

    左点击就传入负值,右点击就传入正值,然后执行图片切换的函数 imgChange() :

    function imgChange(offset) {
        var newLeft = parseInt(slide_imgs.style.left) + offset, // 新坐标值
    
        ...
    
        slide_imgs.style.left = newLeft + 'px';
        if (newLeft > -400) {
            slide_imgs.style.left = -1600 + 'px';
        }
        if (newLeft < -1600) {
            slide_imgs.style.left = -400 + 'px';
        }
    
        ...
    
    }

    imgChange()函数的作用是:

    利用的传入的偏移值,计算出 .slide-imgs元素 该具有的新的位置值(left值),值计算出来后,再设置 . slide-imgs元素 的样式属性。

    当向左切换,切换超出第一张图片的时候,将 .slide-imgs元素 的位置赋值为最后第四张图片的位置值;

    当向右切换,切换超出第四张图片的时候,将 .slide-imgs元素 的位置赋值为最后第一张图片的位置值(如上面的if语句),这样就能实现循环切换。

    按钮点击

    当点击按钮的时候我们要做两件事情:

    1.切换到对应的图片;

    2.改变按钮的样式。

    要做到这两件事我们就需要一个值 slide_index 来记住当前图片和圆点的位置索引:

    <div class="slide-btns" id="slide_btns">
        <span class="slide-btn active" data-index="0"></span>
        <span class="slide-btn" data-index="1"></span>
        <span class="slide-btn" data-index="2"></span>
        <span class="slide-btn" data-index="3"></span>
    </div>
    slide_btns = document.getElementById('slide_btns').getElementsByTagName('span'),
    slide_wrapper = document.getElementById('slide_wrapper'),
    slide_index = 0,  // 当前图片或圆点的索引
    for (var i=0; i<slide_btns.length; i++) {
        slide_btns[i].onclick = function() {
            
            ...
    
            var data_index = parseInt(this.getAttribute('data-index'));
            var offset = -400 * (data_index - slide_index);
    
            ...
    
            imgChange(offset);
            slide_index = data_index;
            btnChange();
        };
    }

    我们先完成第一件事:切换到对应的图片。

    从html代码可以看出,我们给每一个按钮都赋予了相应的位置索引值 data-index。

    接着,我们再给每一个按钮添加点击事件,当点击按钮的时候,我们再调用imgChange()函数,并传入正确的偏移量的值。

    因为点击按钮的时候是可以跳跃性地点击,比如点第一个按钮后点第二个按钮,但也可以点第一个按钮后就点第四个按钮,

    所以传入的偏移量通过这句代码来计算:

    // data_index是点击的按钮的位置索引
    // slide_index是当前图片的位置索引
    var
    offset = -400 * (data_index - slide_index);

    所以当我们执行imgChange()函数后要记得更新当前图片的索引值:

    slide_index = data_index;

    最后是完成第二件事:改变按钮的样式:

    function btnChange() {
        for (var i=0; i<slide_btns.length; i++) {
            if (slide_btns[i].className = 'slide-btn active') {
                slide_btns[i].className = 'slide-btn';
            }
        }
        slide_btns[slide_index].className = 'slide-btn active';
    }

    分析一下上面的代码,事先将选中按钮的样式 .active 写好:

    .active {
        background-color: #20217e;
    }

    再改变选中按钮的类名(className),赋予它 active :

    slide_btns[slide_index].className = 'slide-btn active';

    前提,我们要先利用循环清除每一个按钮的 .active ,这样才不会出现多个选中按钮:

    for (var i=0; i<slide_btns.length; i++) {
        if (slide_btns[i].className = 'slide-btn active') {
            slide_btns[i].className = 'slide-btn';
        }
    }

    滑动过渡

    上面我们所实现都效果都是一瞬间的,就是一瞬间就切换到下一张图片,并没有一种过渡的视觉体验。

    如何实现这种滑动过渡的效果?

    我们知道,轮播图的核心原理是位置偏移,就是从一个位置到另一个位置的改变,

    如果我们为这种改变加上一段时间,假设这个时间是2s,那么完成这个变化就得花2s时间,就不是一瞬间完成了,

    这个完成过程就是过渡,这个过渡因为时间够长,所以能被我们所观察到。

    好了,现在我们有一段时间 time 了,但还不够,

    因为滑动的实质是慢慢地偏移,将一段完整的偏移量分为几次小的偏移量,

    假设我们的 time = 500ms,我们每一次所花时间 perTime = 10ms,

    所以在500ms内,我们要偏移多少次 per = time / perTime ,

    每一次偏移的距离为 perOffset = offset / per 。

    一直地偏移,如果还没到达偏移的目标量时,还是继续偏移,

    这里我们用一个延时定时器和递归来达到这个效果,每 10ms 就进行一次偏移:

    function imgChange(offset) {
        var newLeft = parseInt(slide_imgs.style.left) + offset, // 新坐标值
            time = 500,                                         // 总移动时间
            perTime = 10,                                       // 每次移动的时间
            per = time / perTime,                               // 移动多少次
            perOffset = offset / per;                           // 每次移动的距离
            animate = function() {
                animated = true;
                // 注意这里的parseInt(slide_imgs.style.left)是一个实时的值,不能赋给一个变量以节省代码
                if ((perOffset < 0 && parseInt(slide_imgs.style.left) > newLeft) || 
                    (perOffset > 0 && parseInt(slide_imgs.style.left) < newLeft)) {
    
                    slide_imgs.style.left = parseInt(slide_imgs.style.left) + perOffset + 'px';
                    setTimeout(animate, perTime);
    
                } else {
                    animated = false;
                    slide_imgs.style.left = newLeft + 'px';
                    if (newLeft > -400) {
                        slide_imgs.style.left = -1600 + 'px';
                    }
                    if (newLeft < -1600) {
                        slide_imgs.style.left = -400 + 'px';
                    }
                }
            };
        animate();
    }

    两张辅助图

    如果没有设置辅助图做衔接的话,假设从第四张图滑向第一张图,第四张图会先滑向一片空白区域:

    而不是

    自动播放

    用一个定时器自动地去调用箭头点击函数:

    function autoPlay() {
        timer = setInterval(function() {
            slide_right.onclick();
        }, 2000);
    }
    
    ...
    
    autoPlay(); // 在结尾的时候调用自动播放函数

    源码

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>原生js轮播图</title>
        <style>
            .slide-wrapper {
                position: relative;
                width: 400px;
                height: 250px;
                overflow: hidden;
            }
            .slide-imgs {
                position: absolute;
                z-index: 1;
                width: 2400px;
                height: 250px;
            }
            .slide-img {
                float: left;
                width: 400px;
                height: 250px;
            }
            .slide-btns {
                position: absolute;
                right: 0;
                bottom: 5px;
                left: 0;
                z-index: 2;
                width: 60px;
                margin: auto;
            }
            .slide-btn {
                display: inline-block;
                float: left;
                width: 10px;
                height: 10px;
                margin: 0 2px;
                background-color: #fff;
                border-radius: 50%;
                cursor: pointer;
            }
            .active {
                background-color: #20217e;
            }
            .slide-arrow {
                position: absolute;
                top: 0;
                bottom: 0;
                z-index: 2;
                width: 23px;
                height: 23px;
                margin: auto;
                color: #fff;
                font-size: 30px;
                font-weight: bold;
                line-height: 23px;
                text-decoration: none;
            }
            #slide_left {
                left: 5px;
            }
            #slide_right {
                right: 5px;
            }
        </style>
    </head>
    <body>
        <div class="slide-wrapper" id="slide_wrapper">
            <div class="slide-imgs" id="slide_imgs" style="left: -400px;">
                <img class="slide-img" src="slide-img3.jpg"><!-- 辅助过渡图 -->
                <img class="slide-img" src="slide-img0.jpg">
                <img class="slide-img" src="slide-img1.jpg">
                <img class="slide-img" src="slide-img2.jpg">
                <img class="slide-img" src="slide-img3.jpg">
                <img class="slide-img" src="slide-img0.jpg"><!-- 辅助过渡图 -->
            </div>
            <div class="slide-btns" id="slide_btns">
                <span class="slide-btn active" data-index="0"></span>
                <span class="slide-btn" data-index="1"></span>
                <span class="slide-btn" data-index="2"></span>
                <span class="slide-btn" data-index="3"></span>
            </div>
            <a class="slide-arrow" id="slide_left" href="#">&lt;</a>
            <a class="slide-arrow" id="slide_right" href="#">&gt;</a>
        </div>
        <script>
    
            var slide_left = document.getElementById('slide_left'),
                slide_right = document.getElementById('slide_right'),
                slide_imgs = document.getElementById('slide_imgs'),
                slide_btns = document.getElementById('slide_btns').getElementsByTagName('span'),
                slide_wrapper = document.getElementById('slide_wrapper'),
                slide_index = 0,  // 当前图片或圆点的索引
                animated = false, // 标记是否处于动画状态
                timer = null;
                
    
            function imgChange(offset) {
                var newLeft = parseInt(slide_imgs.style.left) + offset, // 新坐标值
                    time = 500,                                         // 总移动时间
                    perTime = 10,                                       // 每次移动的时间
                    per = time / perTime,                               // 移动多少次
                    perOffset = offset / per;                           // 每次移动的距离
                    animate = function() {
                        animated = true;
                        // 注意这里的parseInt(slide_imgs.style.left)是一个实时的值,不能赋给一个变量以节省代码
                        if ((perOffset < 0 && parseInt(slide_imgs.style.left) > newLeft) || 
                            (perOffset > 0 && parseInt(slide_imgs.style.left) < newLeft)) {
    
                            slide_imgs.style.left = parseInt(slide_imgs.style.left) + perOffset + 'px';
                            setTimeout(animate, perTime);
    
                        } else {
                            animated = false;
                            slide_imgs.style.left = newLeft + 'px';
                            if (newLeft > -400) {
                                slide_imgs.style.left = -1600 + 'px';
                            }
                            if (newLeft < -1600) {
                                slide_imgs.style.left = -400 + 'px';
                            }
                        }
                    };
                animate();
            }
    
            function btnChange() {
                for (var i=0; i<slide_btns.length; i++) {
                    if (slide_btns[i].className = 'slide-btn active') {
                        slide_btns[i].className = 'slide-btn';
                    }
                }
                slide_btns[slide_index].className = 'slide-btn active';
            }
    
            function autoPlay() {
                timer = setInterval(function() {
                    slide_right.onclick();
                }, 2000);
            }
    
            function stopPlay() {
                clearInterval(timer);
            }
    
            slide_left.onclick = function() {
                if (!animated) { // 防止快速点击后出现BUG
                    imgChange(400);
                    if (slide_index == 0) {
                        slide_index = 3;
                    } else {
                        slide_index -= 1;
                    }
                    btnChange();
                }
            };
    
            slide_right.onclick = function() {
                if (!animated) {
                    imgChange(-400);
                    if (slide_index == 3) {
                        slide_index = 0;
                    } else {
                        slide_index += 1;
                    }
                    btnChange();
                }
            };
    
            for (var i=0; i<slide_btns.length; i++) {
                slide_btns[i].onclick = function() {
                    if (this.className != 'slide-btn active') {
                        var data_index = parseInt(this.getAttribute('data-index'));
                        var offset = -400 * (data_index - slide_index);
                        if (!animated) {
                            imgChange(offset);
                            slide_index = data_index;
                            btnChange();
                        }
                    }
                };
            }
    
            slide_wrapper.onmouseover = stopPlay;
            slide_wrapper.onmouseout = autoPlay;
    
            autoPlay();
    
        </script>
    </body>
    </html>
    View Code

    参考视频:https://www.imooc.com/learn/18

  • 相关阅读:
    第二周
    7月课程
    自定义线程池的创建
    jvm8内存模型和内存分配
    多线程中 CountDownLatch CyclicBarrier Semaphore的使用
    HashMap Hashtable TreeMap LinkedHashMap 分析
    HashSet LinkedHashSet TreeSet 分析
    ArrayList Vector LinkedList分析
    Java之流水号生成器实现
    mysql-mmm 部署高可用集群
  • 原文地址:https://www.cnblogs.com/linxian95/p/9791279.html
Copyright © 2011-2022 走看看