requestAnimationFrame需递归调用、用timestamp计算deltaTime实现匀速动画,并通过cancelAnimationFrame安全停止以避免内存泄漏。

直接用 requestAnimationFrame 替代 setTimeout 或 setInterval 就能显著提升动画流畅度,但仅替换函数名远远不够——漏掉时间戳、递归控制或退出逻辑,动画照样卡顿、跳帧甚至内存泄漏。
为什么 requestAnimationFrame 调用后动画只闪一下就停
因为 requestAnimationFrame 不是循环器,它只承诺“在下一帧执行一次回调”。没在回调里再次调用自己,就等于只画了一帧。
- 错误写法:
requestAnimationFrame(animate);后没有在animate函数体内再调一次requestAnimationFrame(animate) - 正确结构必须是:更新状态 → 条件判断 → 再请求下一帧(可选)
- 常见误操作:把
requestAnimationFrame放在事件监听器里反复触发(如多次点击),导致多个独立动画帧注册,主线程瞬间过载
如何用 timestamp 实现匀速动画,避免快慢不一
requestAnimationFrame 回调函数自动传入一个高精度时间戳(timestamp),单位毫秒,从页面加载开始计时。用它计算增量,才能让动画速度与帧率无关。
- 别用
new Date().getTime()或自增变量(如x += 2),它们在掉帧时会累积误差,导致动画忽快忽慢 - 推荐模式:记录上一帧时间 → 计算本次与上次的时间差(
deltaTime = timestamp - lastTime)→ 用deltaTime驱动位移/旋转等变化 - 示例中移动速度设为 100px/s,则每帧位移应为
100 * (deltaTime / 1000)
怎样安全停止动画并防止内存泄漏
停止动画不只是“不再调用”,关键是取消已注册但尚未执行的帧回调。否则 requestAnimationFrame 会持续排队,即使元素已销毁。
立即学习“前端免费学习笔记(深入)”;
- 必须用变量保存上一次调用的返回值,例如:
let animationId = requestAnimationFrame(animate) - 停止时务必调用
cancelAnimationFrame(animationId),且确保animationId是最新有效值 - 在条件退出分支里,不要只写
return,而要先调用cancelAnimationFrame再退出 - 如果动画绑定在某个 DOM 元素上,该元素被
remove()后未清理animationId,就会造成隐式内存泄漏
Canvas 动画中 requestAnimationFrame 的特殊注意事项
Canvas 动画常配合 clearRect() 和路径绘制,但仅清像素不重置绘图状态,会导致路径残留、意外连线。
- 每次
requestAnimationFrame回调中,绘制前必须调用ctx.beginPath(),否则lineTo会延续上一帧的终点 - 避免在
requestAnimationFrame外部做耗时计算(如大量数据处理),否则会挤占渲染时间,引发掉帧 - 若需多对象同步动画,复用同一个
requestAnimationFrame回调,在内部遍历更新,而非为每个对象单独注册
真正决定动画是否“平滑”的,从来不是帧率数字,而是每一帧是否在正确时机完成正确工作——时间戳校准、状态及时清理、绘图上下文重置,这些细节一旦遗漏,60fps 也会看起来像幻灯片。


















