使用Framer Motion与React实现的文字轮播为何随机重叠?
问题:Framer Motion文字轮播切换窗口后出现单词重叠
我正尝试使用Framer Motion在React中实现无限文字轮播,每次显示一个单词,每3秒切换到下一个单词。以下代码多数情况下运行正常,但如果保持页面打开、切换窗口或进行其他操作后返回页面,words数组中的多个单词常会同时显示并重叠。我已为此困扰许久,无法确定触发原因,也不知如何复现该问题。
代码示例
const words = ["Developer", "Nerd", "Humanist", "Mathlete", "Creative", "Coder", "Filmmaker", "Artist", "Listener"] const TextCycle: React.FC = () => { const [index, setIndex] = useState(0) useEffect(() => { const intervalId = setInterval(() => { setIndex(prevIndex => (prevIndex + 1) % words.length); }, 3000) return () => clearInterval(intervalId) }, []); return ( <> <div style={{ overflow: 'hidden', height: '1em' }}> <AnimatePresence initial={false}> <motion.div style={{ position: 'absolute' }} key={index} initial={{ y: '-80%', opacity: 0 }} animate={{ y: '0%', opacity: 1 }} exit={{ y: '80%', opacity: 0 }} transition={{ duration: 0.37 }} > <StyledTextCycle> {words[index]} </StyledTextCycle> </motion.div> </AnimatePresence> </div> </> ); }; export default TextCycle
已尝试的方法
- 在页面可见性变化时重置计时器
- 添加
initial={false} - 加快动画速度反复观察,但问题触发无规律
解决方案
1. 替换setInterval为递归setTimeout
浏览器后台会节流setInterval的执行频率,导致页面回到前台时多个任务集中触发,瞬间多次更新index,而退出动画还未完成就挂载新元素,引发重叠。改用递归setTimeout可确保每次切换严格间隔3秒:
useEffect(() => { let timeoutId: NodeJS.Timeout; const nextWord = () => { setIndex(prev => (prev + 1) % words.length); timeoutId = setTimeout(nextWord, 3000); }; timeoutId = setTimeout(nextWord, 3000); return () => clearTimeout(timeoutId); }, []);
2. 给AnimatePresence添加wait模式
强制等待上一个元素的退出动画完成后再挂载新元素,从根源避免重叠:
<AnimatePresence initial={false} mode="wait"> {/* 原motion.div内容 */} </AnimatePresence>
3. 优化页面可见性监听
在页面隐藏时清除定时器,显示时重新启动,彻底避免后台累积任务:
useEffect(() => { let timeoutId: NodeJS.Timeout; let visibilityChangeHandler: () => void; const nextWord = () => { setIndex(prev => (prev + 1) % words.length); timeoutId = setTimeout(nextWord, 3000); }; visibilityChangeHandler = () => { if (document.hidden) { clearTimeout(timeoutId); } else { timeoutId = setTimeout(nextWord, 3000); } }; document.addEventListener('visibilitychange', visibilityChangeHandler); timeoutId = setTimeout(nextWord, 3000); return () => { clearTimeout(timeoutId); document.removeEventListener('visibilitychange', visibilityChangeHandler); }; }, []);
4. 修正父容器定位
给父容器添加position: relative,确保绝对定位的motion.div处于正确布局上下文,避免视觉偏移导致的重叠:
<div style={{ overflow: 'hidden', height: '1em', position: 'relative' }}> {/* AnimatePresence内容 */} </div>
内容的提问来源于stack exchange,提问作者Adam Bedient
相关产品推荐
相关产品推荐

