在很久以前,有写过一个使用 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">
<!-- 横图 -->
<!-- <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);
}
}
};
};
}