You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 19:30:08