JS轮播效果图基础版

201 阅读4分钟

用JS实现轮播效果图

html代码

    <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>

CSS代码

    * {
        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;
    }

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].className = "";
        focus.children[i].classList.remove("active");
    }
    // 4.3.2 将我们需要的 哪一个 焦点 添加高亮(添加 active 类名)
    // focus.children[index - 1].className = "active";
    focus.children[index - 1].classList.add("active");

    // 4.4 执行到这里的时候, 说明这次运动已经完全结束了, 所以可以打开开关变量, 方便下次点击能够正常执行
    flag = true;
}

/**
 *  5. 移入移出事件
 *      1. 移入时停止   (结束定时器)
 *      2. 移出时开启   (再次调用 autoPlay 函数)
 */
mouseMove();
function mouseMove() {
    // 5.1 鼠标移入  结束定时器
    // banner.onmouseover = function () { clearInterval(timer) };
    // banner.onmouseover = () => { clearInterval(timer) };
    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()
        }
    }
}