JavaScript-实现轮播图(滚动效果)

214 阅读4分钟

1.首先准备一个html文件,将轮播图的框架搭建好,并引入两个JS文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>轮播图</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        .banner {
            width: 600px;
            height: 400px;
            border: 5px solid black;
            margin: 200px auto;
            overflow: hidden;
            position: relative;
        }

        .banner .img_box {
            width: 500%;
            height: 100%;
            list-style: none;
            display: flex;
            position: absolute;
            left: 0px;
        }

        .banner .img_box li {
            width: 600px;
            height: 100%;
            font-size: 50px;
            text-align: center;
            line-height: 400px;
        }

        .banner .focus {
            width: 200px;
            height: 30px;
            background-color: white;
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            bottom: 30px;
            border-radius: 20px;
            display: flex;
            justify-content: space-evenly;
            align-items: center;
            list-style: none;
        }

        .banner .focus li {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background-color: rosybrown;
            cursor: pointer;
        }

        .banner .focus .active {
            background-color: red;
        }

        .left, .right {
            position: absolute;
            transform: translateY(-50%);
            font-size: 50px;
            color: white;
            top: 50%;
            cursor: pointer;
        }

        .left {
            left: 30px;
        }

        .right {
            right: 30px;
        }
    </style>
</head>
<body>
    <div class="banner">
        <!-- 放置所有轮播图的盒子 -->
        <ul class="img_box">
            <li style="background-color: burlywood;">1</li>
            <li style="background-color: royalblue;">2</li>
            <li style="background-color: greenyellow;">3</li>
            <li style="background-color: pink;">4</li>
            <li style="background-color: turquoise;">5</li>
        </ul>
        <!-- 放置焦点的盒子 -->
        <ol class="focus">
            <!-- <li class="active"></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li> -->
        </ol>
        <!-- 左右按钮 -->
        <div class="left">&lt;</div>
        <div class="right">&gt;</div>
    </div>
    <script src="./move.js"></script>
    <script src="./index.js"></script>
    <script>
     /**
     * 轮播图功能:
     *    1. 打开页面 图片自动滚动
     *    2. 鼠标移入停止,鼠标移出开始
     *    3. 点击按钮切换上下页,点击焦点切换图片
     * */
    </script>
</body>
</html>

2.move.js文件(运动函数)

function move(ele, options, fn) {
    //创建一个变量作为计数器
    let count = 0;
    for(let key in option) {
        let type = key;
        let target = options[key];
        //循环每执行一次,让计数器自增一次,因为循环执行一次说明要开启一个运动
        count++;
        //1.开启定时器
        const timer = setInterval(() => {
            //2.获取当前位置
            let init = parseInt(window.getComputedStyle(ele)[type]);
            //3.计算本次移动距离 (目标 - 当前值) / 10
            let duration = (target - init) / 10;
            //4.判断 移动距离如果小于1,那么向上取整
            duration = duration > 0 ? Math.ceil(duration) : Math.floor(duration);
            //5.判断走还是不走
            if(target === init) {
                clearInterval(timer);
                //每清除一个定时器,说明有一个运动函数结束了,所以我们将count--
                count--;
                // 当前分支执行,说明所有的运动函数全都执行完毕了
                if(count === 0) {fn()}
            } else {
                // 元素原本的位置 + 要移动的距离,然后赋值给元素
                ele.style[type] = init + duration + 'px'
            }
        }, 20)
    }
}

3.index.js

//0.获取DOM
const imgBox = document.querySelector(".img_box");
const banner = document.querySelector(".banner");
const focus = document.querySelector(".focus");
//0.准备变量
const bannerWidth = parseInt(window.getComputedStyle(banner).width);
let index = 1;//内部存储的值表明当前在第几张图片
let timer = 0;//内部存储定时器ID
let flag = true; //开关变量,用于控制不能重复触发运动事件

/**
* 1.通过JS 处理前后两张'假图'
*    1.1 将ul 内最后一个 li复制出来,放在ul的开头
*    1.2 将ul 内第一个li 复制出来,放在ul的末尾
*/
copyEle();
function copyEle() {
    // 1.1 复制出来两个 li
    const fakeFirst = imgBox.firstElementChild.cloneNode(true);
    const fakeLast = imgBox.lastElementChild.cloneNode(true);
    
    // 1.2 将复制出来 li 放在 对应的位置
    imgBox.appendChild(fakeFirst);
    imgBox.insertBefore(fakeLast, imgBox.firstElementChild);
    
    // 1.3 处理 ul 的宽度
    imgBox.style.width = imgBox.children.length * bannerWidth + "px";
    
    // 1.4 处理 ul 的定位
    imgBox.style.left = -(index * bannerWidth) + "px";
}
// 2. 根据imgBox 内部的图片数量,决定渲染几个焦点
setFocus();
function setFocus(){
    // 2.1 获取到 图片数量
    const liNum = imgBox.children.length - 2;
    // 2.2 根据图片数量, 创建出对应数量的 li 并且 插入到 DOM 节点中
    for(let i = 0; i < liNum; i++) {
        // 2.2.1 创建 li 节点
        const newLi = document.createElement("li");
        newLi.classList.add("focus_item");
        // 将元素的下标存储在标签上
        newLi.dataset.id = i;
        
        // 2.2.2 给第一个 li 添加一个类名
        // if (i === 0) newLi.classList.add('active')
        i === 0 && newLi.classList.add("active");
        
        // 2.2.3 将创建出来的节点, 插入到元素中
        focus.appendChild(newLi);
    }
}
/**
 * 3. 自动轮播
 *      => 假设 每间隔 3000ms 切换到下一张
 *          + [0]   对应 第一张假图
 *          + [1]   对应 第 1 张
 *          + [2]   对应 第 2 张
 *          + [3]   对应 第 3 张
 *          + .....
 *          + [6]   对应 最后一张假图
 *
 *      => 目前是在 [1] 此时的定位 -600px
 *          + 3000ms 后应该是在 [2], 此时定位应该是 -1200px
 *          + 6000ms 后应该是在 [3], 此时定位应该是 -1800px
 *          + 若干秒后 应该是在 [x], 此时定位应该是 -(x * 轮播图可视区域的宽度)
 *
 *      => 自动轮播核心: 跟随定时器 改变 index(内部存储的值代表当前在 第几张图)
 */
 autoPlay();
 function autoPlay() {
     timer = setInterval(() => {
         // 修改当前页的值
         index++;
         // 在定时器内部改变 ul 的定位
         move(imgBox, {left: -(index * bannerWidth)}, moveEnd)
     }, 1000)
 }
 /**
 *  4. 在某一张图片运动完成之后, 需要将 ul 重新定位到 最开始的位置(第一张真图)
 */
 function moveEnd() {
     // 4.1 当运动结束后, 将 ul 的定位 拉回到 第一张真图的位置
     if(index === imgBox.children.length - 1) {
         imgBox.style.left = -600 + "px";
         index = 1;
     }
     
     // 4.2 当运动到第一张假图的时候, 立马跳转到 最后一张真图
     if(index === 0) {
         index = imgBox.children.length - 2;
         imgBox.style.left = -(index * bannerWidth) + "px";
     }
     
     // 4.3 调整焦点高亮
     // 4.3.1 将 所有的 焦点 取消高亮(去掉 active 类名)
     for(let i = 0; i < focus.children.length; i++) {
         focus.children[i].classList.remove("active");
     }
     // 4.3.2 将我们需要的 哪一个 焦点 添加高亮(添加 active 类名)
     focus.children[index - 1].classList.add("active");
     
     // 4.4 执行到这里的时候, 说明这次运动已经完全结束了, 所以可以打开开关变量, 方便下次点击能够正常执行
     flag = true;
 }
 /**
 *  5. 移入移出事件
 *      1. 移入时停止   (结束定时器)
 *      2. 移出时开启   (再次调用 autoPlay 函数)
 */
 mouseMove();
 function mouseMove() {
     // 5.1 鼠标移入  结束定时器
     banner.onmouseover = () => clearInterval(timer);
     
     // 5.2 鼠标移出 再次调用 autoPlay
     banner.onmouseout = () => autoPlay();
 }
 /**
 *  6. 添加点击事件
 *      + 左右按钮
 *      + 各个焦点
 *      + 因为都是 banner 的后代元素
 *      + 所以我们可以利用事件冒泡将事件委托给父级
 */
 clickBtn();
 function clickBtn() {
     banner.onclick = function (e) {
         // 6.1 上一页
         if(e.target.className === "left") {
             // 先判断开关变量, 决定能否执行下边的代码
             if(flag === false) return;
             // 代码运行到这个地方, 说明要开始运动了, 所以修改开关变量的值, 让他下次点击不能正常执行
             flag = false;
              // 修改当前页的值
              index--;
              move(imgBox, {left: -(index * bannerWidth)}, moveEnd);
         }
         // 6.2 下一页
         if(e.target.className === "right") {
              // 先判断开关变量, 决定能否执行下边的代码
              if(flag === false) return;
              // 代码运行到这个地方, 说明要开始运动了, 所以修改开关变量的值, 让他下次点击不能正常执行
              flag = false;
              // 修改当前页的值
              index++;
              move(imgBox, {left: -(index * bannerWidth)}, moveEnd);
         }
         // 6.3 点击各个焦点
         if(e.target.className === "focus_item") {
             // 先判断开关变量, 决定能否执行下边的代码
             if(flag === false) return;
             // 代码运行到这个地方, 说明要开始运动了, 所以修改开关变量的值, 让他下次点击不能正常执行
             flag = false;
             // 跳转到对应的图片     - 0 是因为 dataset 获取到的是一个字符串, 如果直接 +1, 那么不是加法, 而是拼接
             index = e.target.dataset.id - 0 + 1;
             move(imgBox, {left: -(index * bannerWidth)}, moveEnd);
         }
     };
 }
 /**
 *  7. 关闭页面后(最小化), 重新打开页面时, 轮播图抖动
 *
 *      浏览器最小化之后 DOM 的运动就会停止
 *      但是代码中的 定时器并不会停止
 *
 *      所以再次打开的时候, 会重新执行 DOM 的运动, 但是此时可能堆积了 很多个运动函数
 *
 *
 *      解决: 最小化页面时, 停止定时器
 *              重新打开时, 开启定时器
 */
 overoutDocument();
 function overoutDocument() {
     document.onvisibilitychange = function () {
         //console.log(document.visibilityState)
         /**
         *  document.visibilityState: 
         *          hidden  => 最小化页面
         *          visible => 重新打开页面
        */
        if(document.visibilityState === 'hidden') {
            clearInterval(timer)
        }
        if(document.visibilityState === 'visible') {
            autoPlay()
        }
     }
  }