JavaScript 实现轮播图

505 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第33天,点击查看活动详情

轮播图是我们前端程序员必学的知识点之一,js轮播图有很多种,有简洁的,也有复杂的,越复杂越能考察我们掌握的js基础能力,面试和工作中使用原生写轮播图的场景都不多,但想要实现一个功能齐全的js轮播图还是有一点难度,下面我们来实现一个稍微复杂一点的。

  • 首先来看实现以后的效果是什么:

Snipaste_2022-12-21_18-53-11.png

介绍一下,主要功能点有这么几点

    1. 自动轮播功能
    1. 点击左侧和右侧的按钮 可以切换上一页下一页
    1. 点击下面的红色锚点,可以切换到指定图片
  • 4.左上角区域有一个白色的字,那个是装饰,代码中大家能看到

那么从这些功能点当中能够考察我们的什么水平呢?

首先来看实现需求,实现上代码里没有标签,全靠 js 操作 DOM, 左侧和右侧的按钮要求使用 canvas 绘制,要求图片在切换的时候有过渡效果,下面的红色锚点需要跟随当前选中图片切换样式,鼠标移入图片上的时候暂停轮播。

    1. DOM 操作
    1. canvas 基本能力
    1. 事件侦听操作
    1. 封装能力
    1. JavaScript 基本功

下面我将会放代码,详细的操作我都写在了代码里面的注释当中,每个方法都有很详细的解释,大家可以放心食用~,那么图片地址的话,url 大家随便找几张自己本地的图片,把路径改改,代码一粘贴,就可以看到效果了,因为没有标签,所以只有 cssjs 代码:

  • css
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        .carousel {
            width: 100%;
            height: 33.3vw;
            min-height: 365px;
            position: relative;
            overflow: hidden;
        }

        .img-con {
            width: 200%;
            height: 100%;
            min-width: 1200px;
            position: absolute;
            left: 0;
        }

        .img-con img {
            width: 100%;
            height: 100%;
        }

        .img-con .title {
            position: absolute;
            left: 15%;
            top: 10%;
            color: white;
            font-size: 20px;
            user-select: none;
        }

        .img-con .day {
            font-size: 30px;
        }

        .img-con .image-item {
            width: 50%;
            height: 100%;
            float: left;
            position: relative;
        }

        .dot {
            list-style: none;
            position: absolute;
            margin: 0;
            padding: 0;
            bottom: 30px;
            left: 50%;
            transform: translate(-50%, 0);
        }

        .dot a {
            display: block;
            width: 20px;
            height: 20px;
            border-radius: 20px;
            border: 2px solid red;
            transition: all 0.5s;
        }

        .dot>li {
            margin-left: 10px;
            float: left;
        }

        .dot>li:first-child {
            margin-left: 0;
        }

        .left,
        .right {
            position: absolute;
            top: 50%;
            transform: translate(0, -50%);
        }

        .left {
            left: 20px;
        }

        .right {
            right: 20px;
        }
    </style>
  • js
    <script>
        var list = [{
                img: "./img/a.jpg",
                date: "2/Nov.2021",
                info: "食欲拯救计划|阿一古~是贼拉好吃的延吉啊!"
            },
            {
                img: "./img/b.jpg",
                date: "1/Nov.2021",
                info: "徽州古村落赏秋自驾9日攻略!给这个秋天一个说走就走的理由!"
            },
            {
                img: "./img/c.jpg",
                date: "31/Oct.2021",
                info: "致,荆棘鸟与淘金先驱者——三赴西澳自驾笔记"
            },
            {
                img: "./img/d.jpg",
                date: "30/Oct.2021",
                info: "我是你的俘虏【跟新疆私奔】"
            },
            {
                img: "./img/e.jpg",
                date: "29/Oct.2021",
                info: "遇见阳朔的光与影,晨与昏,山与水"
            },
        ]

        /**
         * imgCon:获取图片所在的div
         * dot:下面的小点
         * prev:记录上一个下点的记录
         * itemList:轮播的整个里面的每个 图片和文字的这一整个div以 0~4 存储
         * bnList:轮播的下面的小圆圈存储 
         * pos:记录当前的轮播应该的下标位置
         * direction:轮播向哪个方向滚动
         * x:记录当前轮播所在的div的left
         * moveBool:记录点击是否可以用
         * speed:移动的速度
         * autoBool:判断轮播是否可以自动轮播
         * time:防抖
         * LEFT:表示当前轮播向左方向移动
         * RIGHT:表示当前轮播向右方向移动
         */
        var imgCon, dot, prev;
        var itemList = [],
            bnList = [],
            pos = 0,
            direction = "left",
            x = 0,
            moveBool = false,
            speed = 50,
            autoBool = false,
            time = 200;
        const LEFT = Symbol(),
            RIGHT = Symbol();
        init();

        /**
         * 1.先创建一个div,用于存储整个轮播,再将轮播的className设置为carousel
         * 2.创建轮播图片的DOM,包括里面的标题等
         * 3.创建轮播下面的小点
         * 4.创建左右按钮
         * 5.将创建的轮播div添加到body里面
         * 6.执行动画,让轮播可以自己滚动
         * 7.设置最开始执行页面时,第一个小点可以添加背景颜色
         * 8.给轮播div添加鼠标移入移出事件,让鼠标进入时,停止动画,离开时开始动画
        */
        function init() {
            var carousel = document.createElement("div");
            carousel.className = "carousel";
            createImageCon(carousel);
            createDot(carousel);
            createBnList(carousel);
            document.body.appendChild(carousel);
            animation();
            changePrev();
            carousel.addEventListener("mouseenter", mouseHandler);
            carousel.addEventListener("mouseleave", mouseHandler);
        }

        /**
         * 功能:鼠标滑入滑出轮播的侦听事件
         * 1.设置是否自动播放autoBool的值
         * 2.判断e.type是否是移入事件,是则赋值false,否则赋值true,并且将防抖的时间time设置为200
        */
        function mouseHandler(e) {
            autoBool = e.type === "mouseenter" ? false : (time = 200 && true);
        }

        /**
         * 功能:创建小点
         * 1.创建一个ul,然后设置ul的className为dot
         * 2.然后根据list,使用reduce返回一个字符串,并将list的索引添加到创建的a标签中
         * 3.将创建好的ul添加到carousel
         * 4.侦听dot小点的点击事件
        */
        function createDot(carousel) {
            dot = document.createElement("ul");
            dot.className = "dot";
            dot.innerHTML = list.reduce((value, item, i) => {
                return value += `<li><a href='#${i}'></a></li>`;
            }, "");
            carousel.appendChild(dot);
            dot.addEventListener("click", dotClickHandler);
        }

        /**
         * 创建轮播图片,然后将创建的添加到最大的轮播div中
         * 1.创建一个装载第一个图片的div,设置className是img-con
         * 2.向创建出来的轮播小div添加list中的第一个数据,然后执行getImageItem(0)
         * 3.将创建好的div添加到carousel中
        */
        function createImageCon(carousel) {
            imgCon = document.createElement("div");
            imgCon.className = "img-con";
            imgCon.appendChild(getImageItem(0));
            carousel.appendChild(imgCon);
        }

        /**
         * 功能:创建左右按钮
         * 1.循环两次分别创建
         * 2.判断,执行createBn,传入一个布尔参数,真则创建了一个左边的按钮,否则创建了一个右边的按钮
         * 3.根据i和0进行比较,真则设置按钮的className值为"left",否则“right”
         * 4.将创建好的按钮添加到轮播中
         * 5.将按钮添加到bnList里面
         * 6.添加按钮的点击事件
        */
        function createBnList(carousel) {
            for (var i = 0; i < 2; i++) {
                var bn = createBn(i === 0);
                bn.className = i === 0 ? "left" : "right";
                carousel.appendChild(bn);
                bnList.push(bn);
                bn.addEventListener("click", bnClickHandler);
            }
        }

        /**
         * 功能:创建一个canvas
         * 1.创建一个新的canvas
         * 2.设置canvas的宽高分别为30和60
         * 3.设置canvas的背景颜色为白色半透明
         * 4.设置canvas里面创建的是2d内容
         * 5.填充canvas里面是纯白色
         * 6.设置canvas的阴影
         * 7.设置canvas的起点和终点,并将其连接
         * 8.判断传入的参数是否为真,为真则表示创建左边的按钮画布,否则创建的是右边的,则将创建的左边的画布进行翻转
         * 9.将创建好的canvas返回
        */
        function createBn(left) {
            var canvas = document.createElement("canvas");
            canvas.width = 30;
            canvas.height = 60;
            canvas.style.backgroundColor = "rgba(0,0,0,0.4)";
            var ctx = canvas.getContext("2d");
            ctx.fillStyle = "#FFF";
            ctx.shadowBlur = 3;
            ctx.shadowOffsetX = 2;
            ctx.shadowOffsetY = 2;
            ctx.shadowColor = "#666";
            ctx.beginPath();
            ctx.moveTo(10, 30);
            ctx.lineTo(20, 15);
            ctx.lineTo(20, 45);
            ctx.closePath();
            ctx.fill();
            if (!left) canvas.style.transform = "scale(-1,1) translate(0,-50%)";
            return canvas;
        }

        /**
         * 功能:根据传入的参数,获取list的一条数据,将这条数据添加到div里面
         * 1.判断itemList里面是否存在list[n],避免创建重复的div,如果itemList里面存在,则直接返回该元素
         * 2.如果itemList里面没有,则先创建一个div,将该div的名字设置为image-item
         * 3.然后根据参数在list里面获取的数据创建html文件,然后添加到新创建的div中,
         * 4.将创建好的div添加到itemList里面,然后返回itemList[n]
        */
        function getImageItem(n) {
            if (itemList[n]) return itemList[n];
            var div = document.createElement("div");
            div.className = "image-item";
            div.innerHTML = `<img src="${list[n].img}">
                <div class='title'>
                    <div>${list[n].date.match(/(\d*)\/(.*)/).slice(1).reduce((value,item,i)=>{
                        if(i===0) value+=`<span class='day'>${item}</span>`;
                        else value+="/"+item;
                        return value;
                    },"")}</div>
                    <span>${list[n].info}</span>
            </div>`
            itemList[n] = div;
            return itemList[n];
        }


        /**
         * 功能:轮播小点的点击事件
         * 1.判断当前点击的是否是小点里面的a标签,如果不是则直接跳出
         * 2.获取当前点击的小点的索引
         * 3.判断当前点击的小点是否大于全局的轮播图片pos,如果大于,则将LEFT的值赋值给direction,否则将RIGHT赋值
         * 4.将当前点击的下标index的值赋值给pos
         * 5.然后创建当前轮播图片的下一张图片createNextImg()函数
        */
        function dotClickHandler(e) {
            if (e.target.nodeName !== "A") return;
            var index = Array.from(dot.children).indexOf(e.target.parentElement);
            direction = index > pos ? LEFT : RIGHT;
            pos = index;
            createNextImg();
        }


        /**
         * 功能:左右按钮的点击事件
         * 1.如果当前点击的是左按钮,则让当前的pos--,如果pos小于0,则又将列表的长度-1赋值给pos,让它从最后一个向前轮播
         * 2.然后将direction设置为RIGHT
         * 3.如果当前点击的是右按钮,则让当前的pos++,如果pos大于列表的最后一个下标,则又将0赋值给pos,让它从第一个向后轮播
         * 4.然后将direction设置为LEFT
         * 5.然后执行创建下一个轮播图片的函数
        */
        function bnClickHandler(e) {
            if (this.className === "left") {
                pos--;
                if (pos < 0) pos = list.length - 1;
                direction = RIGHT;
            } else {
                pos++;
                if (pos > list.length - 1) pos = 0;
                direction = LEFT;
            }
            createNextImg();
        }


        /**
         * 功能:当切换当前轮播图片执行的函数,创建一个新的轮播图片
         * 1.如果当前direction是LEFT,则表示当前的轮播图片要向向左移动,则表示要在当前轮播图片 后面 根据当前pos去获取一个新的图片去添加在后面,然后
         *   轮播图片可以先不做移动,现在轮播图片还是之前的轮播图片,下一张要显示的轮播图片在当前轮播图片的**后面**
         * 2.如果当前direction是RIGHT,则表示当前的轮播图片要向右移动,则表示要在当前轮播图片 前面 根据pos去获取一个新的图片添加在当前轮播图片前面,
         *   然后将整个轮播图片移动一张轮播图片的宽度大小,移动之后,当前轮播还是之前的轮播图片,下一张要显示的轮播图片在当前轮播图片的**前面**
         * 3.然后将moveBool修改为true,表示当前可以移动轮播图片
         * 4.改变轮播小点的样式
        */
        function createNextImg() {
            if (direction === LEFT) {
                x = 0;
                imgCon.appendChild(getImageItem(pos));
            } else if (direction === RIGHT) {
              x = -getImageItem(pos).offsetWidth;
                imgCon.insertBefore(getImageItem(pos), imgCon.firstElementChild);
            }
            imgCon.style.left = x + "px";
            moveBool = true;
            changePrev();
        }

        /**
         * 功能:改变上一个轮播小点的背景颜色
         * 1.判断上一个轮播图是否存在,如果存在,则将该小点的背景设置为红色透明
         * 2.将当前的轮播图的小点位置的小点赋值给prev
         * 3.并将当前prev所代表的小点背景设置为红色不透明
        */
        function changePrev() {
            if (prev) {
                prev.style.backgroundColor = "rgba(255,0,0,0)";
            }
            prev = dot.children[pos].firstElementChild;
            prev.style.backgroundColor = "rgba(255,0,0,1)";
        }


        /**
         * 功能:实现自动轮播
         * 1.requestAnimationFrame(animation);
         * 2.执行moveAnimation();函数
         * 3.执行自动轮播函数
        */
        function animation() {
            requestAnimationFrame(animation);
            moveAnimation();
            autoMove();
        }

        /**
         * 功能:移动轮播的动画
         * 1.如果moveBool为false,表示不能移动当前轮播,跳出
         * 2.如果direction是LEFT,则表示当前轮播图片要向左移动,则使控制轮播定位的left值x减去speed,如果x小于了-当前轮播的宽度,
         *   则将当前轮播移除,然后将后面的轮播图移动到最前面,设置x=0,然后设置不能移动轮播
         * 3.如果direction是RIGHT,则表示当前轮播图片要向右移动,则使控制轮播定位的left值x加上speed,如果x大于0,则将当前轮播移除,
         *   然后将前面的轮播图移动到最后面,设置x=0,然后设置不能移动轮播
         * 4.设置轮播图定位left为x
        */
        function moveAnimation() {
            if (!moveBool) return;
            if (direction === LEFT) {
                x -= speed;
                if (x <= -imgCon.firstElementChild.offsetWidth) {
                    imgCon.firstElementChild.remove();
                    x = 0;
                    moveBool = false;
                }
            } else if (direction === RIGHT) {
                x += speed;
                if (x >= 0) {
                    imgCon.lastElementChild.remove();
                    x = 0;
                    moveBool = false;
                }
            }
            imgCon.style.left = x + "px";
        }

        /**
         * 功能:轮播图自动移动
         * 1.判断是否可以自动轮播,由鼠标是否移入移出轮播决定
         * 2.设置防抖的time--,然后判断time是否大于0,则跳出,否则将time重新设置为200
         * 3.让右边的按钮自动抛发一个点击的事件
        */
        function autoMove() {
            if (!autoBool) return;
            time--;
            if (time > 0) return;
            time = 200;
            bnList[1].dispatchEvent(new MouseEvent("click"));
        }
    </script>