前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >requestAnimationFrame实现单张图片无缝持续滚动

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

作者头像
蓓蕾心晴
发布2022-05-09 15:23:20
3.5K0
发布2022-05-09 15:23:20
举报
文章被收录于专栏:前端小叙

背景

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

效果如下

需求描述

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

代码实现

代码语言:javascript
复制
<!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://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事件触发之后,依然获取不到图片宽高,而上面我们图片滚动是依赖图片的宽高的,这里需要再加个定时器,轮询获取图片宽高,当确实可以获取到宽高之后,再设置滚动距离,并允许开始滚动。

更新后代码如下:

代码语言:javascript
复制
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);
                }
            }
        };
    };
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 效果如下
  • 需求描述
  • 代码实现
  • 备注
  • 参考链接
  • 发现的坑
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档