背景在很久以前 , 有写过一个使用 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 {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"><!-- 横图 --><!-- <imgsrc="http://img.qxzm.cc/220601/1516462647-4.jpg"/> --><!-- 竖图 --><imgsrc="http://img.qxzm.cc/220601/1516464936-5.jpg"/></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 = https://tazarkount.com/read/0;switch (direction) {case"left":// 向左滚动 , 值为 translateXtransformV = "translateX";// 置为 0 的边界值为图片宽度boundaryValue = https://tazarkount.com/read/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:// 向上滚动 , 值为 translateYtransformV = "translateY";// 置为 0 的边界值为图片高度boundaryValue = https://tazarkount.com/read/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;// 设置图片容器的 transformimgWrap.style.transform = `${transformV}(-${distance}px)`;// 关键行:当移动距离大于边界值时 , 重置 distance=0if (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 更新
【requestAnimationFrame实现单张图片无缝持续滚动】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 = https://tazarkount.com/read/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':// 向左滚动 , 值为 translateXtransformV = 'translateX';// 置为 0 的边界值为图片宽度boundaryValue = https://tazarkount.com/read/imgWidth;// 克隆的图片个数 , 至少克隆一张const num1 = Math.ceil(containerWidth / imgWidth) || 1;if(imgElem){for (let index = 0; index < num1; index++) {// 克隆一张图片并插入到图片最后面imgWrap.appendChild(imgElem.cloneNode(false));}}break;default:// 向上滚动 , 值为 translateYtransformV ='translateY';// 置为 0 的边界值为图片高度boundaryValue = https://tazarkount.com/read/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;// 设置图片容器的 transformimgWrap.style.transform = `${transformV}(-${distance}px)`;// 关键行:当移动距离大于边界值时 , 重置 distance=0if (distance >= boundaryValue) {distance = 0;}// 再次调用滚动动画requestId = requestAnimationFrame(scroll);};// 执行滚动动画 , 传入滚动动画回调函数requestId = requestAnimationFrame(scroll);}}, 40);setTimeout(()=>{clearInterval(getCanScrollStatus);},100000)},// 暂停动画pause: () => {if(requestId){cancelAnimationFrame(requestId);}}};};}
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
