React实现列表垂直无缝滚动跑马灯的问题与解决
实现垂直无缝滚动的列表跑马灯
需求与问题
需要实现一个垂直自动滚动的列表跑马灯效果,要求列表滚动到末尾后无缝衔接重复显示,形成连续动画。
最初尝试通过CSS动画结合React的setState每25秒追加列表项,但实际效果是列表滚动到末尾后会跳回开头,无法实现无缝衔接,动画周期结束后会刷新重启出现断层。
初始问题代码
import styled, { keyframes } from "styled-components"; import React from "react"; // 动画关键帧 const marqueeTop = keyframes` 0% { top: 0%; } 100% { top: -100%; } `; // 样式组件 const MarqueeWrapper = styled.div` overflow: hidden; margin: 0 auto !important; `; const MarqueeBlock = styled.div` width: 100%; height: 44vh; flex: 1; overflow: hidden; box-sizing: border-box; position: relative; float: left; `; const MarqueeInner = styled.div` position: relative; display: inline-block; animation: ${marqueeTop} 25s linear infinite; animation-timing-function: linear; &:hover { animation-play-state: paused; /* 鼠标悬停时暂停动画 */ } `; const MarqueeItem = styled.div` transition: all 0.2s ease-out; `; export default class MarqueeContainer extends React.Component { constructor(props) { super(props); this.state = { itemsToShow: this.props.items, // 从父组件获取列表项 }; } // 追加列表项以实现重复渲染 scrollItems = () => { setInterval(() => { this.setState((prevState) => { const newItems = [...prevState.itemsToShow]; newItems.push(this.props.items); return { itemsToShow: newItems }; }); }, 25000); }; componentDidMount() { this.scrollItems(); } render() { return ( <MarqueeWrapper> <MarqueeBlock> <MarqueeInner> {this.state.itemsToShow.map((item, index) => ( <MarqueeItem key={index}>{item}</MarqueeItem> ))} </MarqueeInner> </MarqueeBlock> </MarqueeWrapper> ); } }
修正后的有效代码
通过提前生成多组重复列表项,并优化动画实现方式,解决了无缝衔接的问题:
import styled, { keyframes } from "styled-components"; import React from "react"; const marqueeTop = keyframes` 0% { transform: translateY(0); } 100% { transform: translateY(-100%); } `; const MarqueeWrapper = styled.div` overflow: hidden; margin: 0 auto !important; `; const MarqueeBlock = styled.div` width: 100%; height: 44vh; flex: 1; overflow: hidden; box-sizing: border-box; position: relative; float: left; `; const MarqueeInner = styled.div` position: relative; display: inline-block; animation: ${marqueeTop} 120s linear infinite; animation-timing-function: linear; &:hover { animation-play-state: paused; } `; const MarqueeItem = styled.div` transition: all 1s ease-out; `; export default class MarqueeContainer extends React.Component { constructor(props) { super(props); this.state = { itemsToShow: this.generateMarqueeItems(), }; } scrollItems = () => { setInterval(() => { this.setState({ itemsToShow: this.generateMarqueeItems() }); }, 120000); }; componentDidMount() { this.scrollItems(); } generateMarqueeItems = () => { const items = this.props.items; const repeatCount = 5; const marqueeItems = Array.from({ length: repeatCount }, () => items).flat(); return marqueeItems; }; render() { return ( <MarqueeWrapper> <MarqueeBlock> <MarqueeInner>{this.state.itemsToShow && this.state.itemsToShow.map((item, index) => <MarqueeItem key={index}>{item}</MarqueeItem>)}</MarqueeInner> </MarqueeBlock> </MarqueeWrapper> ); } }
核心优化点
- 改用
transform: translateY实现动画,避免top定位带来的高度计算误差,动画更流畅 - 提前生成多组(示例为5组)重复的列表项,让动画滚动到末尾时,后续内容与开头完全一致,视觉上形成无缝衔接
- 调整动画时长至120秒,配合多组列表,确保滚动过程中不会提前到达内容末尾
- 定时刷新时重新生成多组列表,而非持续追加,避免DOM节点无限增长
内容的提问来源于stack exchange,提问作者Jay Vu
相关产品推荐
相关产品推荐

