requestanimationframe动画流畅的关键是匹配浏览器渲染节奏:必须用回调参数currenttime计算deltatime控制速度,避免强制同步布局(如读offsettop或多次写style),且需批量dom操作、合理使用transform、精准管理cancelanimationframe时机。

用 requestAnimationFrame 做动画,不是“换了个函数就变流畅”,而是必须匹配浏览器渲染节奏、正确计算时间差、避免 DOM 误读写——否则照样卡顿掉帧。
为什么直接替换 setInterval 会更卡
把 setInterval(animate, 16) 换成 requestAnimationFrame(animate) 却没变流畅?大概率是没处理好两件事:一是没用回调参数 currentTime 算时间差,二是还在每帧反复读取 offsetTop 或写 style.left 触发强制同步布局。
- 直接写
this.x += 2→ 在 30fps 设备上速度只剩一半,动画变慢;在高刷屏上又过快 - 每帧调用
element.getBoundingClientRect()→ 强制浏览器立刻回流,打断渲染流水线 - 连续多次修改
style属性(比如先改left再改top)→ 多次触发重排,性能雪崩
必须传参并用 deltaTime 控制运动速度
requestAnimationFrame 回调函数的第一个参数是高精度时间戳(DOMHighResTimeStamp),单位毫秒,比 Date.now() 更准。丢掉它,等于放弃帧率无关性。
正确做法是记录上一帧时间,算出本次耗时:
let lastTime = 0;
function animate(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
<p>// 目标速度:每秒移动 100px
this.x += (100 / 1000) * deltaTime;</p><p>requestAnimationFrame(animate);
}
requestAnimationFrame(animate);</p>
- 别用
setTimeout风格的固定增量(如x += 1.6),那只是赌设备刚好 60fps -
deltaTime是真实流逝毫秒数,不是理论间隔,能自动适配 30/60/120Hz 屏幕 - 首次调用
currentTime可能为 0,所以初始化lastTime用performance.now()更稳妥
DOM 操作必须批量、延迟、最小化
动画循环里最常被忽略的性能杀手,是“读-写-读-写”式 DOM 访问。浏览器会在你读取布局信息(如 offsetHeight)时,立刻 flush 掉所有待生效的样式变更,强制同步计算。
- 把所有 DOM 读操作集中到帧开头(如缓存
clientWidth),所有写操作集中到帧末尾(如一次性设置transform) - 优先用
transform和opacity动画,它们走合成层,不触发布局和绘制 - 避免在动画中调用
getComputedStyle、scrollIntoView、focus()等隐式强制布局的方法
cancelAnimationFrame 的时机和状态管理
cancelAnimationFrame 不是“暂停动画”的万能开关,它只取消下一次回调注册。如果当前帧还在执行中,或状态已更新但未渲染,就可能产生撕裂或跳变。
- 暂停时,应设标志位(如
isPaused = true),并在animate开头检查,避免继续更新逻辑 - 恢复动画不能只调
requestAnimationFrame(animate),要确保lastTime重置为当前时间戳,否则deltaTime会极大,导致瞬间飞走 - 元素被
display: none或移出 DOM 后,务必手动cancelAnimationFrame(id),rAF 不会自动清理失效引用
真正难的不是调用 requestAnimationFrame,而是让每一帧都干净:无强制回流、无冗余计算、时间步长自适应、状态与渲染严格解耦。这些细节不处理,再“标准”的调用也只是在浏览器渲染流水线上横冲直撞。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











