美文网首页
简单的无缝轮播图

简单的无缝轮播图

作者: 422ccfa02512 | 来源:发表于2020-12-22 18:38 被阅读0次

    前言

    轮播图出现各大网站上-无论是pc还是移动端,尤其是电商网站必然能看见轮播图,它使得用户不用滚动屏幕就能看到更多内容,也常常作为广告位。而作为一个前端工程师,手写轮播图是一个必备的技能。

    下图展示了京东淘宝腾讯云3个网站的轮播图。最常见的2种轮播图有淡入淡出,无缝轮播。无缝轮播对于用户体验会更好一些。

    京东商城 淘宝 腾讯云

    实现功能

    1. 实现一个含有5张图片的无缝轮播图。
    2. 鼠标悬停在轮播图部分时,轮播图停止切换,鼠标离开继续自动切换。
    3. 通过点击左右2边的按钮,进行轮播图的前一张或后一张的切换。
    4. 在图片动画未切换完成之前,禁止切换下一张图片。

    效果图如下:

    无缝轮播

    何为无缝

    无缝轮播图,即是在图片左右切换时,最后一张和第一张相连,也就是当主屏幕显示最后一张图片时,如果用户点击下一张图片时,这时候需要将第一张图片呈现给用户。同理当目前主屏幕上显示第一张图片时,如果用户点击上一张图片时,需要将最后一张图片呈现给用户。

    处理办法如下图(序号为当前编号的图片):

    初始化轮播图时,我们复制第一张图片与最后一张图片,将复制好的第一张图片放在图片末尾,复制好的最后一张图片放在队列头部。这样当轮播图进行到最后一张时,我们将轮播图位置更改为初始的图片1位置。若我们向左边点击时,遇到图片5时,我们将图片拉到最后一张图片5得位置。这样就不会出现播到最后一张图片后,导致的没图片出现空白的情况。这样就是无缝轮播。

    罗列难点

    1. 滚动到队列末尾时,改为队列第二张图片。
    2. 用户频繁点击切换图片,之前动画未结束造成的显示错乱。
    3. 在图片运动结束后,图片没有完全切换完成的情况。
    4. 图片运动时,等待轮播的计时器未停止。

    布局

    布局这一块的话,基本没有什么大问题。就直接上代码。

    html部分

    <div id="wrap">
            <div id="box-wrap">
                <div class="item">
                    <img src="https://img10.360buyimg.com//babel/jfs/t1/147387/5/18994/136741/5fdc77afE5f82113e/f4c98e84e67f8fd0.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img11.360buyimg.com//babel/jfs/t1/138375/30/18878/225016/5fdcb77fE3ed18d79/71d924b7f529c6ea.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img12.360buyimg.com//babel/jfs/t1/144721/38/17890/108504/5fd37694E682d34fc/9d1ac8a5d13b94f8.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img10.360buyimg.com//babel/jfs/t1/148646/26/18903/93961/5fdc9321E51f8e513/c4324e7ea048805c.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img14.360buyimg.com//babel/jfs/t1/155501/29/10296/70544/5fdc8f2fE6b2fab26/5423c671aa4e21bf.jpg!q80.webp" alt="">
                </div>
            </div>
            <div class="btn">
                <div class="arrow prev" id="left-arrow"><</div>
                <div class="arrow next" id="right-arrow">></div>
            </div>
        </div>
    

    css部分

           *{
                padding: 0;
                margin: 0;
            }
            #wrap{
                position: relative;
                width: 780px;
                height: 400px;
                margin: 50px auto;
                border: 1px solid black;
                overflow: hidden;
                cursor: pointer;
            }
            #box-wrap{
                position: absolute;
                left: 0;
                top: 0;
                display: flex;
            }
            #box-wrap .item{
                width: 780px;
                height: 400px;
            }
            .item img{
                width: 100%;
            }
            .red{
                background-color: red;
            }
            .green{
                background-color: green;
            }
            .black{
                background-color: black;
            }
            .arrow{
                position: absolute;
                top: 50%;
                width: 30px;
                height: 30px;
                transform: translate(0,-50%);
                color: rgb(201, 200, 200);
                font-size: 20px;
                text-align: center;
                line-height: 30px;
                background: rgb(0, 0, 0, .3);
            }
            .arrow:hover{
                background: rgb(0, 0, 0, 1);
                color: white;
            }
            .btn{
                user-select: none;
            }
            .btn .prev{
                left: 0;
            }
            .btn .next{
                right: 0;
            }
    

    功能分析

    如上面的代码完成布局之后,效果如下图,接下来我们就需要让图片自动轮播。

    布局图

    轮播逻辑

    DOM加载完成之后通过setInterval、定位,让图片队列盒子#box-wrap在展示图片的盒子中进行移动,即随着时间的变化改变DOM(#box-wrap)的left值。关键代码如下。

    let time = null
    time = setInterval(()=>{
           nextRun()
    },5000)
    
    
    nextRun = () => {
        if (index === itemLength - 1) { // 当图片达到最后一张时,赋值为第一张
            index = 1
            boxWrap.style.left = -itemWidth*index+"px"       // 这里的itemWidth为单张图片宽度
        }
        ++index
        move(boxWrap, itemWidth, 15) // 调用运动函数
    }
    
    move = (el, target, speed) => {
        let s = parseInt(boxWrap.style.left) // 当前图片的移动距离
        let t = target/speed // 计算时间,总位移距离/单次跑的步长
        let s1 = 0 // 当前初始位移
    
        let time2 = setInterval(()=>{
            s1 += speed
            el.style.left = -s1 + s + "px"
            
            if (s1 + speed > target) {
                el.style.left = -target + s + "px"
                clearInterval(time2)
            }
        }, t)
    }
    

    发现计算步长始终会少那么一点点,最后需要补齐,我们把最后一个参数修改为总时间t,这样时间是会减少到0的。

    nextRun = () => {
        if (index === itemLength - 1) { // 当图片达到最后一张时,赋值为第一张
            index = 1
            boxWrap.style.left = -itemWidth*index+"px"       // 这里的itemWidth为单张图片宽度
        }
        ++index
        move(boxWrap, itemWidth, 1000) // 调用运动函数
    }
    
    move = (el, target, t) => {
        let s = parseInt(boxWrap.style.left)
        let s1 = 0
        let speed = target/t
    
        let time2 = setInterval(()=>{
            s1 += speed
            el.style.left = -s1 + s + "px"
            t--
            if (t === 0) {
                clearInterval(time2)
            }
        }, 1)
    }
    

    这里发现向右的轮播正常了,但是发现时间明明设置的5秒钟的自动轮播,为什么不到5秒就执行了。这里当动画轮播启动时,需要终止自动轮播的计时器,结束以后再重新轮播,下面是关键代码。

    run = (fn) => {
        time = setInterval(()=>{
            fn ? nextRun(fn) : nextRun()
        },3000)
    }
    
    run(run)
    
    move = (el, target, t, fn) => {
        let s = parseInt(boxWrap.style.left)
        let s1 = 0
        let speed = target/t
        
        clearInterval(time)
    
        let time2 = setInterval(()=>{
            s1 += speed
            el.style.left = -s1 + s + "px"
            t--
            if (t === 0) {
                if (fn) fn(fn)
                clearInterval(time2)
            }
        }, 1)
    }
    
    nextRun = (fn) => {
        if (index === itemLength - 1) {
            index = 1
            boxWrap.style.left = -itemWidth*index+"px"       
        }
    
        ++index
    
        fn ?
        move(boxWrap, itemWidth, 500, fn) :
        move(boxWrap, itemWidth, 500)
    
    }
    

    完整代码

    发现一个向右的自动轮播就完成了,向左同理改造move的第三个参数type为轮播方向,接下来的点击切换就简单了,下面是一个无缝轮播的完整代码。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
        *{
            padding: 0;
            margin: 0;
        }
        #wrap{
            position: relative;
            width: 780px;
            height: 400px;
            margin: 50px auto;
            border: 1px solid black;
            overflow: hidden;
            cursor: pointer;
        }
        #box-wrap{
            position: absolute;
            left: 0;
            top: 0;
            display: flex;
        }
        #box-wrap .item{
            width: 780px;
            height: 400px;
        }
        .item img{
            width: 100%;
        }
        .red{
            background-color: red;
        }
        .green{
            background-color: green;
        }
        .black{
            background-color: black;
        }
        .arrow{
            position: absolute;
            top: 50%;
            width: 30px;
            height: 30px;
            transform: translate(0,-50%);
            color: rgb(201, 200, 200);
            font-size: 20px;
            text-align: center;
            line-height: 30px;
            background: rgb(0, 0, 0, .3);
        }
        .arrow:hover{
            background: rgb(0, 0, 0, 1);
            color: white;
        }
        .btn{
            user-select: none;
        }
        .btn .prev{
            left: 0;
        }
        .btn .next{
            right: 0;
        }
        </style>
    </head>
    <body>
        <div id="wrap">
            <div id="box-wrap">
                <div class="item">
                    <img src="https://img10.360buyimg.com//babel/jfs/t1/147387/5/18994/136741/5fdc77afE5f82113e/f4c98e84e67f8fd0.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img11.360buyimg.com//babel/jfs/t1/138375/30/18878/225016/5fdcb77fE3ed18d79/71d924b7f529c6ea.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img12.360buyimg.com//babel/jfs/t1/144721/38/17890/108504/5fd37694E682d34fc/9d1ac8a5d13b94f8.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img10.360buyimg.com//babel/jfs/t1/148646/26/18903/93961/5fdc9321E51f8e513/c4324e7ea048805c.jpg!q80.webp" alt="">
                </div>
                <div class="item">
                    <img src="https://img14.360buyimg.com//babel/jfs/t1/155501/29/10296/70544/5fdc8f2fE6b2fab26/5423c671aa4e21bf.jpg!q80.webp" alt="">
                </div>
            </div>
            <div class="btn">
                <div class="arrow prev" id="left-arrow"><</div>
                <div class="arrow next" id="right-arrow">></div>
            </div>
        </div>
    
        <script>   
            window.onload = () => {
                (()=>{
                    let index = 1
                    let wrap = document.querySelector("#wrap")
                    let boxWrap = document.querySelector("#box-wrap")
                    let imgLen = document.querySelectorAll('#box-wrap div').length
                    let img_first = document.querySelectorAll('#box-wrap div')[0].cloneNode(true)
                    let img_last = document.querySelectorAll('#box-wrap div')[imgLen - 1].cloneNode(true)
                    let leftArrow = document.querySelector("#left-arrow")
                    let rightArrow = document.querySelector("#right-arrow")
                    let itemWidth = document.querySelectorAll("#box-wrap div")[0].offsetWidth
    
                    boxWrap.appendChild(img_first)
                    boxWrap.insertBefore(img_last, document.querySelectorAll("#box-wrap div")[0])
    
                    let itemLength = document.querySelectorAll("#box-wrap div").length
    
                    boxWrap.style.width = itemWidth * itemLength + "px"
                    boxWrap.style.left = -itemWidth * index + "px"
    
                    let time = null
                    let isMove = true
    
                    run = (fn) => {
                        time = setInterval(()=>{
                            fn ? nextRun(fn) : nextRun()
                        },3000)
                    }
    
                    run(run)
    
                    move = (el, target, type, t, fn) => {
                        let s = parseInt(boxWrap.style.left)
                        let s1 = 0
                        let speed = target/t
                        
                        clearInterval(time)
    
                        let time2 = setInterval(()=>{
                            s1 += speed
                            if (type === "left") {
                                el.style.left = -s1 + s + "px"
                            }else{
                                el.style.left = s1 + s + "px"
                            }
                            t--
                            if (t === 0) {
                                isMove = true
                                if (fn && time) fn(fn)
                                clearInterval(time2)
                            }
                        }, 1)
                    }
    
                    wrap.onmouseenter = () => {
                        clearInterval(time)
                        time = null
                    }
    
                    wrap.onmouseleave = () => {
                        run(run)
                    }
    
                    nextRun = (fn) => {
                        if (!isMove) return
                        
                        isMove = false
                        
                        if (index === itemLength - 1) {
                            index = 1
                            boxWrap.style.left = -itemWidth*index+"px"       
                        }
    
                        ++index
    
                        fn ?
                        move(boxWrap, itemWidth, "left", 500, fn) :
                        move(boxWrap, itemWidth, "left", 500)
    
                    }
    
                    prevRun = () => {
                        if (!isMove) return
    
                        isMove = false
                        
                        if (index === 1) {
                            index = itemLength - 1
                            boxWrap.style.left = -itemWidth * index + "px"
                        }
                        --index
                        move(boxWrap, itemWidth, "right", 500)
                    }
    
                    leftArrow.onclick = (e) => {
                        prevRun()
                    }
    
                    rightArrow.onclick = (e) => {
                        nextRun()
                    }
                })()
            }
        </script>
    </body>
    </html>
    

    相关文章

      网友评论

          本文标题:简单的无缝轮播图

          本文链接:https://www.haomeiwen.com/subject/nwisnktx.html