requestAnimationFrame驱动Canvas动画需递归调用、清屏+更新+重绘三步完整、用时间戳计算delta保证速度一致、避免帧内重任务并适配dpr。

用 requestAnimationFrame 驱动 Canvas 动画,核心不是“凑够60次/秒”,而是让每一帧都落在浏览器重绘前的正确时机上。它天然对齐屏幕刷新节奏(通常是60Hz),页面切到后台会自动暂停,不浪费资源,也不掉帧。
必须递归调用,不能只启动一次
requestAnimationFrame 不是定时器,它只触发一帧。想持续动画,得在回调里主动再请求下一帧:
- 定义一个函数(比如 animate),里面写更新逻辑和绘制逻辑
- 在函数末尾写 requestAnimationFrame(animate),形成递归链
- 首次启动只需调用一次 requestAnimationFrame(animate),别写成 animate()
- 若需暂停,保存返回的 ID,用 cancelAnimationFrame(id) 清除;恢复时再调一次 requestAnimationFrame(animate)
清屏 + 更新 + 重绘,三步缺一不可
Canvas 不会自动擦除上一帧。残留图像会叠加,造成拖影或糊成一片:
- 每次绘制前,先调用 ctx.clearRect(0, 0, canvas.width, canvas.height) 或用全屏背景色覆盖
- 更新状态变量(如小人的 x、y 坐标,粒子的速度、位置)
- 再调用绘图命令(如 ctx.fillRect、ctx.beginPath)画出新一帧
- 常见错误:忘了清屏,或把坐标更新写在绘制之后,导致画面不动
用时间戳算 delta,让速度不随帧率浮动
直接写 x += 2 看似简单,但在 30fps 设备上就慢一半,用户感知明显卡顿:
- requestAnimationFrame 回调带一个高精度时间戳(单位毫秒),比 Date.now() 更准
- 记录上一帧时间,本次减去它得到耗时 deltaTime
- 位移按比例计算:比如目标速度是每秒 120px,则 x += (120 / 1000) * deltaTime
- 这样无论设备跑 60fps 还是 45fps,动画快慢都一致
别在动画帧里干重活,渲染优先
主线程被占满,就会挤掉渲染时间,导致掉帧。浏览器无法强制你“准时交画”:
- 避免在 requestAnimationFrame 回调里做大量计算、DOM 查询(如 getBoundingClientRect)、样式读取(如 offsetTop)
- 复杂逻辑(如粒子碰撞检测、路径预计算)可拆到单独函数,用节流控制频率,或放在 Web Worker 里
- 确保 clearRect 和绘图命令尽可能轻量,优先用 transform 或 drawImage 复用图形
- 注意设备像素比(dpr):canvas 的 width/height 属性要设为 clientWidth * dpr,否则高清屏下模糊
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











