正确使用 requestAnimationFrame 需解耦调度与逻辑,避免在 update 内重复调用导致帧率失控,并在 React/Vue 中结合 useRef 和 useEffect/onUnmounted 清理帧请求以防内存泄漏。

动画帧调度不是靠 HTML 实现的
HTML 本身不提供动画帧调度能力,requestAnimationFrame 是 JavaScript API,HTML 只负责承载元素和结构。想在“组件内部”做帧调度,关键在于 JS 层如何绑定生命周期与帧循环,而不是写个 <div animate> 就能生效。
React 组件中用 requestAnimationFrame 的正确姿势
常见错误是直接在 useEffect 里无限调用 requestAnimationFrame 却没清理,导致内存泄漏或帧冲突。正确做法是:
- 用
useRef存储当前帧 ID,每次更新前先cancelAnimationFrame - 把动画逻辑封装成独立函数(比如
animateStep),避免闭包捕获过期 state - 在组件卸载时务必清除帧请求,否则即使组件 unmount 了,帧回调还在执行
- 不要在每次 render 都新建动画函数——它会触发重绑定,破坏帧节奏
function MyComponent() {
const frameRef = useRef();
const [progress, setProgress] = useState(0);
<p>useEffect(() => {
const animate = () => {
setProgress(p => Math.min(p + 0.01, 1));
frameRef.current = requestAnimationFrame(animate);
};
frameRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(frameRef.current);
}, []);</p><p>return <div style={{ width: <code>${progress * 100}%</code> }} />;
}Vue 3 setup 中避免 requestAnimationFrame 被重复启动
Vue 的响应式更新可能触发多次 watch 或 onMounted 回调,尤其在开发模式下热更新时。容易踩的坑包括:
-
onMounted里直接调用requestAnimationFrame,但没用onUnmounted清理 - 在
watch内部启动帧循环,却没判断是否已有活跃帧(导致多个并发动画) - 用
ref存帧 ID,但没用shallowRef或toRef导致不必要的响应式开销
推荐用一个受控标志位 + 帧 ID 双保险:
审查 React Router 代码,确保数据加载、变更、错误处理和导航模式符合规范,适用于 React Router v6.4+ 代码、加载器及其他特性。
立即学习“前端免费学习笔记(深入)”;
const isAnimating = ref(false);
const frameId = ref(0);
<p>onMounted(() => {
const loop = () => {
if (!isAnimating.value) return;
// ... 动画逻辑
frameId.value = requestAnimationFrame(loop);
};
isAnimating.value = true;
frameId.value = requestAnimationFrame(loop);
});</p><p>onUnmounted(() => {
isAnimating.value = false;
cancelAnimationFrame(frameId.value);
});Web Components 里帧调度要绕开 Shadow DOM 的样式隔离陷阱
自定义元素内部用 requestAnimationFrame 更新样式时,常遇到动画卡顿或无效,根本原因不是帧调度本身,而是:
- Shadow DOM 中的
offsetWidth、getBoundingClientRect()等测量操作被浏览器延迟到下一帧,导致帧内计算结果滞后 - CSS
transition和 JS 帧更新混用,产生竞态:比如 JS 刚设transform,CSS 还在 transition 途中,下帧又覆盖了中间状态 - 未启用
will-change: transform,导致频繁重绘无法 GPU 加速
实操建议:
- 测量类操作统一放在
requestAnimationFrame回调开头,避免被 layout thrashing 干扰 - 动画属性尽量只用
transform和opacity,避开触发布局的属性(如width、top) - Shadow DOM 样式中显式加
contain: strict减少重绘范围
帧调度本身很简单,难的是让每一帧都真正“有效”——多数卡顿问题出在测量、渲染、样式三者的时间错位,而不是调度逻辑写错了。


















