JS实现轮播图

(本文章不做过多的解释,基本上每行JS代码都有对应的注释。)

下面代码,复制粘贴就可以拿过去自己尝试一下啦!不过照片需要你自己更换本地图片。

其中有我个人封装的简单运动animate,其中的animate()函数即是,最下面有该运动的源码,需要一起复制到另一个js文件 并用script标签引入来使用,这封装好的简单运动也建议大家看看噢!

本轮播图的主要操作功能有:

①点击左右按钮更换上下页

②拖拽滚动(必须先阻止浏览器的默认行为才能实现拖拽)

③无限轮播

④自动轮播

⑤点击指示器更换页数

⑥鼠标进入区域停止自动轮播、鼠标移出则回复自动轮播

效果图:

以下就是全部代码:

<!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>
    <script src="animate.js"></script>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

    a {
          text-decoration: none;
    }

    li {
    list-style: none;
    }

    img {
    display: block;
    }

    .box {
    width: 600px;
    height: 400px;
    border: 1px solid black;
    margin: 50px auto;
    overflow: hidden;
    position: relative;
    }

    .imgBox {
    width: 500%;
    height: 100%;
    display: flex;
    position: absolute;
    left: 0px;
    }

    .imgBox li {
    width: 600px;
    height: 100%;
    }

    .imgBox li img {
    width: 600px;
    height: 100%;
    }

    .lrTabs .prev,.next {
    width: 20px;
    height: 40px;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    background-color: rgb(61, 61, 61);
    text-align: center;
    font: 800 20px/40px "宋体";
    color: #fff;
    cursor: pointer;
    z-index: 10;
    opacity: .5;
    }

    .next {
    right: 0;
    }

    .pointBox {
    display: flex;
    position: absolute;
    justify-content: space-between;
    /* 没有动态变化的小圆点的样式 */
    /* width: 250px; */
    /* left: calc(50% - 115px); */
    left: calc(50% - 60px);
    bottom: 20px;
    }

    .pointBox li {
    /* 没有动态变化的小圆点的样式 */
    /* width: 40px;
    height: 5px; */
    /* border-radius: 12%; */
    width: 30px;
    height: 4px;
    border-radius: 10%;
    background-color: #ccc;
    cursor: pointer;
    margin-right: 10px;
    transition: all .9s;
    }

    .pointBox .active {
    background-color: aquamarine;
    }
    </style>
</head>

<body>
    <div class="box">
        <ul class="imgBox">
            <li class="slider"><img src="./img/02.jpg"></li>
            <li class="slider"><img src="./img/03.jpg"></li>
            <li class="slider"><img src="./img/07.jpg"></li>
            <li class="slider"><img src="./img/09.jpg"></li>
            <li class="slider"><img src="./img/10.jpg"></li>
        </ul>

        <ol class="pointBox"></ol>

        <div class="lrTabs">
            <a class="prev">&lt;</a>
            <a class="next">&gt;</a>
        </div>
    </div>

    <script>
        var box = document.querySelector(".box")
        var ul = document.querySelector(".imgBox")
        var prev = document.getElementsByClassName("prev")
        var next = document.getElementsByClassName("next")
        var lis = document.querySelectorAll(".slider")
        var lisWidth = parseFloat(getComputedStyle(lis[0]).width)
        var lrTabs = document.querySelector(".lrTabs")
        var ol = document.querySelector(".pointBox")
        var imgs = document.querySelectorAll("img")

        var currentIndex = 1
        // 封装切换上一张图的函数
        function prev1() {
            --currentIndex
            animate(
                ul, {
                    left: -lisWidth * currentIndex + "px"
                },
                500,
                function () {
                    // console.log("动画结束");
                    // console.log(currentIndex);
                    onAnimationEnd()
                }
            )

        }
        // 封装切换下一张图的函数
        function next1() {
            ++currentIndex
            animate(
                ul, {
                    left: -lisWidth * currentIndex + "px"
                },
                500,
                function () {
                    // console.log("动画结束");
                    // console.log(currentIndex);
                    onAnimationEnd()
                }
            )
        }

        // 通过事件委托来实现切换上下图的功能
        lrTabs.addEventListener(
            "click",
            function (e) {
                if (e.target.className === "next") {
                    next1()
                } else if (e.target.className === "prev") {
                    prev1()
                }
            }
        )

        // 实现指示器随着图片的切换而高亮
        var pointers = null

        function lightHeightPointers() {
            pointers.forEach(
                function (p, index) {
                    if (index === currentIndex - 1) {
                        p.classList.add("active")
                        p.style.width = "60px"
                        p.style.height = "4px"
                    } else {
                        p.classList.remove("active")
                        p.style.width = "30px"
                    }
                }
            )
        }

        // 初始化指示器,即添加指示器
        function initPointers() {
            var fragment = document.createDocumentFragment()
            lis.forEach(
                function (l, index) {
                    var li = document.createElement("li")
                    fragment.appendChild(li)
                }
            )
            ol.appendChild(fragment)
            pointers = document.querySelectorAll("ol>li")
            lightHeightPointers()
        }
        initPointers()

        // 初始化无限轮播
        function initLoop() {
            var head = lis[lis.length - 1].cloneNode(true) // 第五张
            var tail = lis[0].cloneNode(true)
            ul.appendChild(tail)
            ul.insertBefore(head, lis[0])
            // 获取ul里的最新全部的li
            var sliders = document.querySelectorAll("ul>li")

            // 根据添加了最新的li来重新定义ul的宽度
            ul.style.width = lisWidth * sliders.length + "px"
            ul.style.left = -lisWidth + "px"
        }
        initLoop()

        // 动画结束回调,真5跳假5,假1跳真1
        function onAnimationEnd() {
            currentIndex >= 6 && (currentIndex = 1)
            currentIndex <= 0 && (currentIndex = 5)

            ul.style.left = -lisWidth * currentIndex + "px"
            lightHeightPointers()
        }

        // 实现 自动轮播
        var timer = null
        // 开始自动轮播
        function startAutoPlay() {
            if (!timer) {
                timer = setInterval(function () {
                    next1()
                }, 3000)
            }
        }
        startAutoPlay()

        // 停止自动轮播
        function stopAutoPlay() {
            if (timer) {
                clearInterval(timer)
                timer = null
            }
        }

        // 实现鼠标移入停止、鼠标移出后继续自动轮播的功能
        box.onmouseenter = function (e) {
            stopAutoPlay()
        }
        box.onmouseleave = function (e) {
            startAutoPlay()
        }

        // 实现点击指示器切换幻灯片功能
        function clickPointers() {
            pointers.forEach(
                function (p, index) {
                    p.onclick = function () {
                        currentIndex = index + 1
                        ul.style.left = -lisWidth * currentIndex + "px"
                        lightHeightPointers()
                    }
                }
            )
        }
        clickPointers()

        // 阻止浏览器默认行为
        imgs.forEach(
            function (img, index) {
                img.addEventListener(
                    "mousedown",
                    function (e) {
                        e.preventDefault()
                    }
                )
            }
        )

        /* 拖拽 */
        // 标记是否正在拖拽
        var isDragging = false

        // 记录鼠标按下时的x位置
        var downX = null

        // 记录鼠标按下时ul的left值
        var downUlLeft = null

        /* 在ul身上按下鼠标,此处的对象要改成最大的div盒子即包含有左右tab的box,因为如果对象为ul时点击左右tab并且tab不属于ul的里面,所以系统会认为鼠标没有按下去但是鼠标松手的对象又是页面,所以会触发鼠标抬起的函数并导致downX的值为null */
        box.onmousedown = function (e) {
            // 标记开始拖拽
            isDragging = true

            // 同时记录鼠标按下的位置 + ul的初始left
            downX = e.pageX
            // console.log("downx","=",e.pageX);
            downUlLeft = parseFloat(ul.style.left)
            // console.log("此处是在onmousedown上按下鼠标");
        }

        /* 鼠标在任意位置撒手 */
        document.onmouseup = function (e) {
            // 不再拖拽ul
            isDragging = false

            // 获取鼠标的总偏移量
            var offsetX = e.pageX - downX
            // console.log("此处是在onmouseup上松开的鼠标");
            // console.log(e.pageX);
            // console.log(downX);
            // console.log(offsetX);

            /* 分情况讨论:上一下 OR 下一张 OR 还原拖拽前位置 */
            switch (true) {
                // 往右狂扫
                case offsetX >= lisWidth / 3:
                    prev1()
                    break;

                    // 往左狂扫
                case offsetX <= -lisWidth / 3:
                    next1()
                    break;
                // case offsetX = 0:
                //     break;
                    // 有气无力地扫了一点点:还原扫前位置
                default:
                    ul.style.left = -lisWidth * currentIndex + "px"
                    break;
            }
        }

        /* 拖拽ul */
        ul.onmousemove = function (e) {
            // 如果鼠标已经down下
            if (isDragging) {
                // 鼠标的当前位置距离down下时的偏移量
                var offsetX = e.pageX - downX

                // 鼠标偏移量即ul的left偏移量
                ul.style.left = downUlLeft + offsetX + "px"
                // console.log("此处是在ul上拖拽鼠标");
            }
        }
    </script>
</body>

</html>

下面是封装好的简单运动源码(注释已是相当详细了):

/* 从样式属性值中截取单位 */
// getUnit("200.123px")
function getUnit(value) {
    // 定义所有的数字
    var digits = "0123456789.-"

    // 将入参先转换为string(以便计算字符数)
    value = value + ""//200px

    /* 遍历入参的每一个字符 */
    for (var i = 0; i < value.length; i++) {
        // 提取出每一个字符
        var char = value[i]

        // 看看当前字符是否是【数字字符】
        // 如果不是【数字字符】 则当前位i一直到末尾皆为单位
        if (digits.includes(char) === false) {
            // 从当前位i一直截取到末尾 得到单位 返回之
            return value.slice(i)
        }

    }
    return ""
}

/**
 * 多属性动画
 * @param {Element} element 要做动画的元素
 * @param {Object} targetObj 属性目标值的对象 封装了所有要做动画的属性及其目标值
 * @param {number} timeCost 动画耗时,单位毫秒
 * @param {Function} callback 动画结束的回调函数
 * animate(box,{left:"200px",top:"500px",opacity:0.5},3000,()=>console.log("动画结束"))
 */
function animate(element, targetObj, timeCost = 1000, callback = null) {

    // 定义每帧时长为40毫秒
    var FRAEME_COST = 40

    // 计算总帧数(半帧按一帧算)3000/40
    var frames = Math.ceil(timeCost / FRAEME_COST)

    // 记录当前运行到了第几帧
    var currentFrame = 0

    // 提前拿到动画前所有CSS属性 {left:0,top:0,opacity:1...}
    var exStyles = window.getComputedStyle(element)

    /* 每个CSS属性每帧应该偏移多少 */
    // speed者 每帧偏移量也
    var speedObj = {}

    /* 计算每帧应该移动多少 */
    // key分别为 left top opacity
    for (var key in targetObj) {
        // 拿出left属性的目标值 200px
        var targetValue = targetObj[key]
        speedObj[key] = {
            // left每帧的偏移量
            offset: (parseFloat(targetValue) - parseFloat(exStyles[key])) / frames,

            // 将单位也揪出来
            unit: getUnit(targetValue)
        }
    }

    /* 开始做动画 */
    var timer = setInterval(

        /* 每40毫秒执行一下这个破函数:在上一帧(frame)的基础上 所有目标样式都按动画速度speedObj[key]偏移一丢丢 */
        function () {

            // 在每帧中对每个属性按动画速度做偏移
            for (var key in speedObj) {

                /* 拿出left属性每帧的偏移量 */
                var offset = speedObj[key].offset

                // 拿出left属性的单位
                var unit = speedObj[key].unit

                // 将元素的left设置为一个新的值(偏移了一丢丢)
                // element.style.left = (0+2)+"px"
                element.style[key] = parseFloat(getComputedStyle(element)[key]) + offset + unit
            }

            /* 看看够帧数了没有 */
            if (++currentFrame === frames) {
                // 帧数一满 停止动画
                clearInterval(timer)

                // 暴力矫正误差(将所有属性都打到目标值)
                // {left:"200px",top:"500px",opacity:0.5}
                for (var key in targetObj) {
                    // box.style.left = "200px"
                    element.style[key] = targetObj[key]
                }

                // 回调callback
                callback && callback()
            }
        },

        // 每40毫秒一帧
        FRAEME_COST
    );
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值