React图片无限滚动动画结束时出现跳变问题求助
无限图片滚动跳变问题修复方案
问题说明
尝试实现图片无限滚动动画时,动画周期结束后图片出现异常跳变,但相同逻辑在文本滚动上正常运行,目标是实现无缝循环的滚动效果。
现有代码
CSS 代码
.outter-scroller{ overflow: hidden; -webkit-mask: linear-gradient( 90deg, transparent, white 20%, white 80%, transparent ); mask: linear-gradient(90deg, transparent, white 20%, white 80%, transparent); } .inner-scroller{ width: max-content; flex-wrap: nowrap; animation: scroll 20s linear infinite; animation-fill-mode: forwards; }
JSX 代码
import { useEffect } from "react" const sourceImages = ['src/assets/ic1.jpg', 'src/assets/ic2.jpg', 'src/assets/ic3.jpg', 'src/assets/ic4.jpg', 'src/assets/ic4.jpg', 'src/assets/ic5.jpg', 'src/assets/ic6.jpg', 'src/assets/ic7.jpg', 'src/assets/ic8.jpg'] const ImageSectionAnimatedAuto = () => { useEffect(() => { const handleAnimation = () => { const scrollerInner = document.querySelector('.inner-scroller') const scrollerContent = Array.from(scrollerInner.children) scrollerContent.forEach((item) => { const duplicatedItem = item.cloneNode(true) scrollerInner.appendChild(duplicatedItem) }) } handleAnimation() }, []) return ( <section className="bg-[#1d283a] w-full h-[100vh] flex flex-col justify-center items-center"> <h1 className="text-6xl text-white text-center">Infinite scroll</h1> <div className="outter-scroller md:w-[60vw] w-[600px] py-10"> <ul className="inner-scroller flex gap-5"> {["HTML", "CSS", "JAVASCRIPT", "SSG", "WEBDEV", "ANIMATION", "UI/UX"].map((item) => ( <li className="m-0 text-white rounded-2xl px-6 py-2 bg-slate-500 shadow-lg" key={item}>{item}</li> ))} </ul> </div> <div className="outter-scroller py-5 flex w-[80vw] overflow-hidden"> <div className="inner-scroller flex gap-5"> {sourceImages.map((image, _i) => ( <img key={_i} src={image} className="aspect-video w-[200px] rounded-xl" /> ))} </div> </div> </section> ) } export default ImageSectionAnimatedAuto
问题根源与修复步骤
1. 缺失关键帧动画定义
现有CSS中没有@keyframes scroll,导致动画无执行逻辑;同时要确保位移距离等于原始内容的宽度,让动画结束时克隆内容刚好衔接,避免跳变。
2. 错误的animation-fill-mode设置
animation-fill-mode: forwards会让动画停在最后一帧,无限滚动不需要这个属性,直接移除。
3. 图片加载时机问题
图片是异步加载的,初始执行克隆逻辑时图片可能未加载完成,导致.inner-scroller宽度计算错误,动画位移偏差。需等待所有图片加载完成后再执行克隆和动画设置。
修复后的完整代码
CSS 代码
.outter-scroller{ overflow: hidden; -webkit-mask: linear-gradient( 90deg, transparent, white 20%, white 80%, transparent ); mask: linear-gradient(90deg, transparent, white 20%, white 80%, transparent); } .inner-scroller{ width: max-content; flex-wrap: nowrap; animation: scroll 20s linear infinite; } /* 新增关键帧动画,位移距离为原始内容宽度的负值 */ @keyframes scroll { to { transform: translateX(-50%); } }
JSX 代码
import { useEffect, useRef } from "react" const sourceImages = ['src/assets/ic1.jpg', 'src/assets/ic2.jpg', 'src/assets/ic3.jpg', 'src/assets/ic4.jpg', 'src/assets/ic4.jpg', 'src/assets/ic5.jpg', 'src/assets/ic6.jpg', 'src/assets/ic7.jpg', 'src/assets/ic8.jpg'] const ImageSectionAnimatedAuto = () => { const imageScrollerRef = useRef(null) useEffect(() => { const handleAnimation = async () => { const scrollerInner = imageScrollerRef.current if (!scrollerInner) return // 等待所有图片加载完成 const images = scrollerInner.querySelectorAll('img') await Promise.all(Array.from(images).map(img => { if (img.complete) return Promise.resolve() return new Promise(resolve => img.onload = resolve) })) // 克隆原始内容 const scrollerContent = Array.from(scrollerInner.children) scrollerContent.forEach(item => { const duplicatedItem = item.cloneNode(true) scrollerInner.appendChild(duplicatedItem) }) } handleAnimation() }, []) return ( <section className="bg-[#1d283a] w-full h-[100vh] flex flex-col justify-center items-center"> <h1 className="text-6xl text-white text-center">Infinite scroll</h1> <div className="outter-scroller md:w-[60vw] w-[600px] py-10"> <ul className="inner-scroller flex gap-5"> {["HTML", "CSS", "JAVASCRIPT", "SSG", "WEBDEV", "ANIMATION", "UI/UX"].map((item) => ( <li className="m-0 text-white rounded-2xl px-6 py-2 bg-slate-500 shadow-lg" key={item}>{item}</li> ))} </ul> </div> <div className="outter-scroller py-5 flex w-[80vw] overflow-hidden"> {/* 使用ref获取图片滚动容器,避免全局选择器冲突 */} <div ref={imageScrollerRef} className="inner-scroller flex gap-5"> {sourceImages.map((image, _i) => ( <img key={_i} src={image} className="aspect-video w-[200px] rounded-xl" /> ))} </div> </div> </section> ) } export default ImageSectionAnimatedAuto
额外说明
- 使用
transform: translateX(-50%)是因为克隆了一份原始内容,总宽度为原始的2倍,位移50%刚好让克隆内容衔接原始内容起点,实现无缝循环。 - 使用
useRef替代document.querySelector,避免多个.inner-scroller时的选择器冲突。 - 等待图片加载完成后再克隆元素,确保容器宽度计算准确,动画位移无偏差。
内容的提问来源于stack exchange,提问作者Felipe Useda
相关产品推荐
相关产品推荐

