前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【云+社区年度征文】简单的无缝轮播图

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

原创
作者头像
_kyle
修改2020-12-22 18:02:13
1.1K0
修改2020-12-22 18:02:13
举报
文章被收录于专栏:kyle的专栏

前言

轮播图出现各大网站上-无论是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>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 实现功能
  • 何为无缝
  • 罗列难点
  • 布局
    • html部分
      • css部分
      • 功能分析
      • 轮播逻辑
      • 完整代码
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档