requestAnimationFrame实现单张图片无缝持续滚动


背景

在很久以前,有写过一个使用 代码,但那一版实现会持续操作DOM,向DOM中插入元素,性能较差,最近发现 requestAnimationFrame 通过 动画的方式实现图片滚动更加方便,遂重新实现了一版,效果更赞,性能更好。

效果如下

需求描述

需要单张图片在可视区域内无缝持续向上滚动或向左滚动,由于向下和向右属于反着坐标轴移动图片,和正常DOM元素插入展示顺序也相反,遂不考虑此种场景。

代码实现

DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>滚动图片title>
        <style>
            /*竖向滚动*/
            #container {
                width: 300px;
                height: 150px;
                overflow: hidden;
                margin: 100px;
            }
            #wrap {
                width: 100%;
                height: auto;
                display: flex;
                flex-direction: column;
                align-items: center;
                transform: translatez(0);
            }

            img {
                width: 100%;
                height: auto;
            }
            /*横向滚动*/
            /* #container {
                width: 300px;
                height: 150px;
                overflow: hidden;
                margin: 100px;
            }
            #wrap {
                width: auto;
                height: 100%;
                display: flex;
                flex-wrap: nowrap;
                align-items: center;
                transform: translatez(0);
            }
            img {
                width: auto;
                height: 100%;
            } */
        style>
    head>
    <body>
        <div id="container">
            <div id="wrap">
                
                
                
                <img
                    src="https://img0.baidu.com/it/u=3724390669,1663648862&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=752"
                    alt=""
                />
            div>
        div>
        <script>
            // 功能:实现图片无缝向上滚动
            // run:运行图片轮播
            // pause:暂停图片轮播
            function imageScroll() {
                if (
                    /iP(ad|hone|od).*OS 13/.test(window.navigator.userAgent) || // iOS6 is buggy
                    !window.requestAnimationFrame ||
                    !window.cancelAnimationFrame
                ) {
                    let lastTime = 0;
                    window.requestAnimationFrame = function (callback) {
                        const now = Date.now();
                        const nextTime = Math.max(lastTime + 16, now);
                        return setTimeout(function () {
                            callback((lastTime = nextTime));
                        }, nextTime - now);
                    };
                    window.cancelAnimationFrame = clearTimeout;
                }
                // 执行动画函数
                const requestAnimationFrame =
                    window.requestAnimationFrame ||
                    window.webkitRequestAnimationFrame ||
                    window.mozRequestAnimationFrame;
                // 取消执行动画函数
                const cancelAnimationFrame =
                    window.cancelAnimationFrame ||
                    window.webkitCancelAnimationFrame ||
                    window.mozCancelAnimationFrame;
            
                // 初始化定义轮播函数
                requestId = null;
                return function () {
                    return {
                        // imgWrap:图片容器,放置多张图片,整体进行滚动
                        // imgView: 图片所展示区域的窗口view
                        // step 每次移动的距离
                        // direction: 滚动方向,默认 "top" 持续向上滚动,可选值为 "top" 和 "left"
                        run: (imgWrap, imgView, step = 1, direction = "top") => {
                            if (!imgWrap || !imgView) {
                                console.warn("请传入参数形如[图片包裹容器,图片展示容器]");
                                return false;
                            }
                            // 获取窗口宽度
                            const containerWidth = parseInt(imgView.clientWidth);
                            // 获取窗口高度
                            const containerHeight = parseInt(imgView.clientHeight);
                            // 获取图片元素
                            const imgElem = imgWrap.querySelector("img");
                            // 获取图片宽度
                            const imgWidth = parseInt(imgElem.width);
                            // 获取图片高度
                            const imgHeight = parseInt(imgElem.height);
                            // 初始化移动距离
                            let distance = 0;
                            // 定义 transform 值名称
                            let transformV;
                            // 初始化图片移动置为0的边界长度
                            let boundaryValue = 0;
                            switch (direction) {
                                case "left":
                                    // 向左滚动,值为 translateX
                                    transformV = "translateX";
                                    // 置为 0 的边界值为图片宽度
                                    boundaryValue = parseFloat(imgWidth);
                                    // 克隆的图片个数,至少克隆一张
                                    const num1 = Math.ceil(containerWidth / imgWidth) || 1;
                                    for (let index = 0; index < num1; index++) {
                                        // 克隆一张图片并插入到图片最后面
                                        imgWrap.appendChild(
                                            imgWrap.querySelector("img").cloneNode(true)
                                        );
                                    }
                                    break;
                                default:
                                    // 向上滚动,值为 translateY
                                    transformV = "translateY";
                                    // 置为 0 的边界值为图片高度
                                    boundaryValue = parseFloat(imgHeight);
                                    // 克隆的图片个数,至少克隆一张
                                    const num2 = Math.ceil(containerHeight / imgHeight) || 1;
                                    for (let index = 0; index < num2; index++) {
                                        // 克隆一张图片并插入到图片最后面
                                        imgWrap.appendChild(
                                            imgWrap.querySelector("img").cloneNode(true)
                                        );
                                    }
                                    break;
                            }
                            // 定义滚动动画回调函数
                            const scroll = () => {
                                // 移动的距离=已经移动的距离+每步的长度
                                distance = distance + step;
                                // 设置图片容器的 transform
                                imgWrap.style.transform = `${transformV}(-${distance}px)`;
                                // 关键行:当移动距离大于边界值时,重置 distance=0
                                if (distance >= boundaryValue) {
                                    distance = 0;
                                }
                               
                                // 再次调用滚动动画
                                requestId = requestAnimationFrame(scroll);
                            };
                            // 执行滚动动画,传入滚动动画回调函数
                            requestId = requestAnimationFrame(scroll);
                           
                        },
                        // 暂停动画
                        pause: () => {
                            cancelAnimationFrame(requestId);
                        },
                    };
                };
            }

            window.onload = () => {
                const scroll = imageScroll();
                // 向左滚动
                // scroll().run(
                //     document.getElementById("wrap"),
                //     document.getElementById("container"),
                //     0.5,
                //     "left"
                // );
                // 向上滚动
                scroll().run(
                    document.getElementById("wrap"),
                    document.getElementById("container"),
                    1,
                    "top"
                );
                // 通过定时器可以实现图片滚动几秒后暂停,如下表示先滚动 4s 后暂停,之后每个隔 2s 再滚动,2秒后再暂停
                // setInterval(() => {
                //     scroll().pause();
                //     setTimeout(() => {
                //         scroll().run(
                //             document.getElementById("wrap"),
                //             document.getElementById("container"),
                //             1,
                //             "top"
                //         );
                //     }, 2000);
                // }, 4000);
            };
        script>
    body>
html>

备注

对于向上滚动和向左滚动两种效果,css 样式要同步修改,支持横图、竖图滚动。

代码中用到了百度图片,侵删。

参考链接

如何设计实现无缝轮播  【同时这里其他朋友答案也都很赞,收藏了】

【相关知识点与优势可参考这里】