【云+社区年度征文】简单的无缝轮播图

2020-12-22 18:02:13 浏览数 (2)

前言

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

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

京东商城京东商城
淘宝淘宝
腾讯云腾讯云

实现功能

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

效果图如下:

无缝轮播无缝轮播

何为无缝

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

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

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

罗列难点

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

布局

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

html部分

代码语言:javascript复制
<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部分

代码语言:javascript复制
       *{
            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值。关键代码如下。

代码语言:javascript复制
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的。

代码语言:javascript复制
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秒就执行了。这里当动画轮播启动时,需要终止自动轮播的计时器,结束以后再重新轮播,下面是关键代码。

代码语言:javascript复制
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为轮播方向,接下来的点击切换就简单了,下面是一个无缝轮播的完整代码。

代码语言:javascript复制
<!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>

0 人点赞