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


    背景

    在很久以前,有写过一个使用 js 实现单张图片持续滚动图片的 代码,但那一版实现会持续操作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 {
                     300px;
                    height: 150px;
                    overflow: hidden;
                    margin: 100px;
                }
                #wrap {
                     auto;
                    height: 100%;
                    display: flex;
                    flex-wrap: nowrap;
                    align-items: center;
                    transform: translatez(0);
                }
                img {
                     auto;
                    height: 100%;
                } */
            </style>
        </head>
        <body>
            <div id="container">
                <div id="wrap">
                    <!-- 横图 -->
                    <!-- <img
                        src="https://img1.baidu.com/it/u=49865366,3040475020&fm=253&fmt=auto&app=138&f=JPEG?w=751&h=500"
                        alt=""
                    /> -->
                    <!-- 竖图 -->
                    <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>
                "use strict";
                // 功能:实现图片无缝向上滚动
                // run:运行图片轮播
                // pause:暂停图片轮播
                // imgWrap:图片容器,放置多张图片,整体进行滚动
                // imgView: 图片所展示区域的窗口view
                // step 每次移动的距离
                // direction: 滚动方向,默认 "top" 持续向上滚动,可选值为 "top" 和 "left"
                function imageScroll(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;
                    }
    
                    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 {
                            run: () => {
                                // 定义滚动动画回调函数
                                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(
                        document.getElementById("wrap"),
                        document.getElementById("container"),
                        1,
                        "top"
                    );
                    // 向左滚动
                    // const scroll = imageScroll(
                    //     document.getElementById("wrap"),
                    //     document.getElementById("container"),
                    //     0.5,
                    //     "left"
                    // );
                    scroll().run();
                    // 通过定时器可以实现图片滚动几秒后暂停,如下表示先滚动 4s 后暂停,之后每个隔 2s 再滚动,2秒后再暂停
                    // setInterval(() => {
                    //     scroll().pause();
                    //         setTimeout(() => {
                    //             scroll().run();
                    //         }, 2000);
                    //     }, 4000);
                };
            </script>
        </body>
    </html>

    备注

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

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

    参考链接

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

    requestAnimationFrame 知多少?【相关知识点与优势可参考这里】

     

    发现的坑

    1、非严格模式下,function中定义的变量 ,如果没写 let  或 const  或 var ,会导致 该方法之后都不会执行,也没有报错

    "use strict" 严格模式下,会报错该变量未定义。

    =====================

    2022.03.09 更新

    2、发现在有些场景下图片onload事件触发之后,依然获取不到图片宽高,而上面我们图片滚动是依赖图片的宽高的,这里需要再加个定时器,轮询获取图片宽高,当确实可以获取到宽高之后,再设置滚动距离,并允许开始滚动。

    更新后代码如下:

    function newScroll(imgWrap, imgView, step = 1, direction = 'top') {
        if (!imgWrap || !imgView) {
            console.warn('请传入参数形如[图片包裹容器,图片展示容器]');
            return false;
        }
        // requestAnimationFrame 向下兼容
        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;
        // 初始化定义轮播函数
        let requestId = null;
        // 获取图片窗口宽度
        const containerWidth = (imgView.clientWidth && parseFloat(imgView.clientWidth)) || document.body.clientWidth;;
        // 获取图片窗口高度
        const containerHeight = (imgView.clientHeight && parseFloat(imgView.clientHeight)) || document.body.clientHeight;
        // 获取图片元素
        const imgElem = imgWrap.querySelector('img');
        // 获取图片宽度
        let imgWidth = 0;
        // 获取图片高度
        let imgHeight = 0;
        // 初始化移动距离
        let distance = 0;
        // 定义 transform 值名称
        let transformV;
        // 初始化图片移动置为0的边界长度
        let boundaryValue = 0;
        // 是否可以执行滚动动画,保证获取到图片真实宽高之后再开始滚动,否则获取不到宽高,始终不会滚动
        let canScroll = false;
        // 定时执行获取图片宽高
        var checkImg = function () {
            // 只要任何一方大于0,表示已经服务器已经返回宽高
            if (imgElem.width > 0 || imgElem.height > 0) {
                console.log("获取到真实宽高,清除定时器",imgElem.width,imgElem.height)
                console.log("定时器获取",new Date().getTime())
                clearInterval(set);
                imgWidth = parseFloat(imgElem.width);
                // 获取图片高度
                imgHeight = parseFloat(imgElem.height);
                switch (direction) {
                    case 'left':
                        // 向左滚动,值为 translateX
                        transformV = 'translateX';
                        // 置为 0 的边界值为图片宽度
                        boundaryValue = imgWidth;
                        // 克隆的图片个数,至少克隆一张
                        const num1 = Math.ceil(containerWidth / imgWidth) || 1;
                        if(imgElem){
                            for (let index = 0; index < num1; index++) {
                                // 克隆一张图片并插入到图片最后面
                                imgWrap.appendChild(imgElem.cloneNode(false));
                            }
                        }
                        break;
                    default:
                        // 向上滚动,值为 translateY
                        transformV = 'translateY';
                        // 置为 0 的边界值为图片高度
                        boundaryValue = imgHeight ;
                        // 克隆的图片个数,至少克隆一张
                        const num2 = Math.ceil(containerHeight / imgHeight) || 1;
                        if(imgElem){
                            for (let index = 0; index < num2; index++) {
                                // 克隆一张图片并插入到图片最后面
                                imgWrap.appendChild(imgElem.cloneNode(false));
                            }
                        }
                        break;
                }
                canScroll = true;
            }
        };
        // 当图片元素存在时再调用定时器
        if(imgElem){
            var set = setInterval(checkImg, 40);
            // 轮询 10 秒,还拿不到就不清除定时器
            setTimeout(()=>{
                clearInterval(set);
            },10000)
        }
    
        return function () {
            return {
                run: () => {
                    // 保证在获取到图片宽高,可以滚动的时候再滚动
                    let getCanScrollStatus = setInterval(()=>{
                        if(canScroll){
                            clearInterval(getCanScrollStatus);
                            // 定义滚动动画回调函数
                            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);
                        }
                    }, 40);
                    setTimeout(()=>{
                        clearInterval(getCanScrollStatus);
                    },100000)
    
                },
                // 暂停动画
                pause: () => {
                    if(requestId){
                        cancelAnimationFrame(requestId);
                    }
                }
            };
        };
    }
  • 相关阅读:
    背景透明的static控件
    MFC应用程序配置不正确解决方案
    #pragma once与 #ifndef的区别
    一种新颖的流程控制方式
    DbgView.exe的应用和使用类
    内存对齐的一点个人理解
    MFC下实现透明位图
    结束已知应用程序名的进程
    基本线程编程
    Linux下PCI设备驱动程序开发的经典文章
  • 原文地址:https://www.cnblogs.com/beileixinqing/p/15946573.html
Copyright © 2020-2023  润新知