requestanimationframe不提升帧率,只对齐浏览器刷新节奏;它自动适配高刷屏、后台暂停、cpu波动,需递归调用、用时间戳实现匀速运动,并配合transform/opacity等高效属性与读写分离策略避免强制同步布局。

requestAnimationFrame 本身不提升帧率,只对齐刷新节奏
浏览器帧率由硬件和渲染管线决定,requestAnimationFrame 不是“提高 FPS”的开关,而是让你的 JS 动画逻辑精准落在每一帧的重绘前。硬写 setTimeout(fn, 16) 无法应对高刷屏(如 120Hz)、页面后台暂停、CPU 负载波动等情况,容易掉帧或跳变。真正卡顿往往来自单帧内超时(>16ms),而非“没用 rAF”。
- 必须每次回调末尾显式调用
requestAnimationFrame,否则只执行一帧 - 页面切到后台时,
requestAnimationFrame自动暂停;setTimeout不会——可能白跑几十秒还占资源 - 传入回调的时间戳(
timestamp)是高精度单调值,可用于实现与帧率无关的匀速运动,避免因掉帧导致位移忽快忽慢
动画属性选错,rAF 再准也卡
用 requestAnimationFrame 驱动 element.style.left 或 element.style.width,等于主动触发 Layout → Paint → Composite 全流程,哪怕每帧只改一次,频繁调用照样掉帧。现代浏览器不会因为加了 will-change: transform 就赦免 top 的开销。
- ✅ 优先用
transform(translateX/scale)和opacity:走合成器线程,不阻塞主线程 - ❌ 避免在 rAF 回调里设置
left、top、width、height、display - ⚠️
transform: translateZ(0)这类“伪硬件加速”已过时,现代浏览器自动优化图层,乱加反而增加管理负担
读写交替是布局抖动的直接推手
在 requestAnimationFrame 回调里先读 offsetTop,马上又设 style.transform,看似无害,但浏览器必须立刻同步回流——把所有未提交的样式计算、布局全部做完,才能返回你想要的值。这个过程无法被 rAF “协调”,它只管调度时机,不管操作类型。
- ✅ 所有读取布局的操作(
getBoundingClientRect()、offsetHeight、computedStyle)集中放在回调最开头,一次性读完 - ✅ 所有写入操作(
style.transform、classList.add)统一放在读取之后,不要穿插 - ❌ 禁止在循环中反复“读高度 → 改 margin”,这会触发多次强制同步布局
预览画面高频更新时,别让 rAF 变成性能放大器
比如 Canvas 预览、滚动跟随、鼠标追踪等场景,事件(scroll、mousemove)可能每秒触发上百次,如果每个事件都立即 requestAnimationFrame,会导致大量冗余回调排队,甚至同一帧内多次执行——不仅没节流,反而加重主线程压力。
- 用
let pending = false+ 闭包标记控制:只在空闲时注册一次 rAF,后续变更暂存,回调中批量应用 - 对非关键动画(如缓慢淡入、尺寸渐变),可用计数器降频,例如“每 3 帧执行一次逻辑”,减少计算压力
- 检测元素是否进入视口?优先用
IntersectionObserver;监听尺寸变化?用ResizeObserver——它们异步、不阻塞渲染,比轮询 + rAF 更轻量
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











