React中scroll事件触发的动画为什么会有延迟和卡顿?

♫乐佳 阅读 23

我用React做页面滚动动画时遇到了问题。当我用window.addEventListener(‘scroll’)监听滚动位置,并用useState更新动画数值时,动画总会有半秒左右的延迟,滑动页面时感觉卡顿。

我试过把更新逻辑放在useEffect里,代码大致是这样的:


useEffect(() => {
  const handleScroll = () => {
    const scrollTop = window.scrollY;
    setScrollPosition(scrollTop); // 更新状态驱动动画
    console.log('scroll detected:', scrollTop);
  };
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, []);

但实际滚动时控制台输出的信息明显滞后于手指滑动屏幕的动作,动画也跟着延迟。我怀疑是不是setState有性能问题?或者应该用requestAnimationFrame来优化?有没有更好的方案解决这个延迟问题?

我来解答 赞 7 收藏
二维码
手机扫码查看
1 条解答
Mc.文博
Mc.文博 Lv1
滚动事件直接用setState更新状态当然会卡,React的state更新是异步的,而且默认没有做节流处理,scroll事件每秒触发几十次,加上render的开销,根本跟不上的。

你猜对了,得上requestAnimationFrame,还得防抖。但别在useEffect里光绑个scroll就完事,那样每次回调还可能有闭包问题。

直接用这个模式:

const [scrollPosition, setScrollPosition] = useState(0);

useEffect(() => {
let frameId = null;

const handleScroll = () => {
if (frameId === null) {
frameId = requestAnimationFrame(() => {
frameId = null;
setScrollPosition(window.scrollY);
});
}
};

window.addEventListener('scroll', handleScroll, { passive: true });

return () => {
window.removeEventListener('scroll', handleScroll, { passive: true });
if (frameId !== null) {
cancelAnimationFrame(frameId);
}
};
}, []);


关键点三个:
第一,requestAnimationFrame让更新和浏览器刷新率同步,一般是60fps,不会丢帧。
第二,加frameId做节流,避免连续排队多个raf任务。
第三,addEventListener加上{ passive: true },告诉浏览器你不会调用preventDefault,提升滚动响应速度,尤其在移动端特别明显。

如果你动画精度要求不高,甚至可以直接用CSS的transform + will-change来驱动视差,比JS更顺滑。但要是必须用JS读滚动位置,上面这套是最稳的。
点赞 4
2026-02-11 13:25