推荐使用 requestanimationframe(raf)实现 canvas 动画循环,因其能自动适配屏幕刷新率、避免丢帧卡顿、后台自动暂停省资源;基础结构含清空画布、更新状态、绘制帧、请求下一帧四步;需缓存上下文、用时间戳控制帧率无关运动、注意像素比与性能优化。

Canvas动画循环推荐用 requestAnimationFrame(简称 rAF),它比 setInterval 或 setTimeout 更高效、更流畅,能自动适配屏幕刷新率,避免丢帧和卡顿。
为什么用 requestAnimationFrame 而不是 setInterval
rAF 由浏览器调度,在下一次重绘前执行,与显示器刷新节奏同步(通常是 60fps);而 setInterval 按固定毫秒数触发,容易和刷新不同步,造成“撕裂”或掉帧。尤其在页面后台运行时,rAF 会自动暂停,节省资源,setInterval 却照常执行,浪费 CPU。
基础动画循环结构
一个标准的 Canvas 动画循环长这样:
function animate() {
// 1. 清空画布(根据需求决定是否清空)
ctx.clearRect(0, 0, canvas.width, canvas.height);
<p>// 2. 更新状态(如小球位置、角度、速度等)
ball.x += ball.vx;
ball.y += ball.vy;</p><p>// 3. 绘制当前帧
ctx.beginPath();
ctx.arc(ball.x, ball.y, 10, 0, Math.PI * 2);
ctx.fill();</p><p>// 4. 请求下一帧
requestAnimationFrame(animate);
}</p><p>// 启动动画
requestAnimationFrame(animate);
</p>
关键细节与实用建议
-
不要在 animate 内直接调用自身递归(如
animate()),必须用requestAnimationFrame(animate)—— 浏览器需要这个引用才能正确调度。 -
控制动画节奏可结合时间戳:rAF 回调会传入高精度时间戳(DOMHighResTimeStamp),可用于计算 Δt,实现帧率无关的运动逻辑:
let lastTime = 0;<br> function animate(timestamp) {<br> const deltaTime = timestamp - lastTime;<br> lastTime = timestamp;<br> // 用 deltaTime 控制位移:ball.x += ball.speed * deltaTime / 16; // 16ms ≈ 60fps<br> requestAnimationFrame(animate);<br> } - 暂停/恢复动画很简单:用一个布尔变量控制是否继续调用 rAF,无需清除定时器;退出时自然停止,无内存泄漏风险。
-
注意 canvas 像素比(devicePixelRatio):高清屏上若忽略缩放,绘制会模糊。初始化时应设置
canvas.width/canvas.height为物理像素,并用 CSS 控制显示尺寸。
常见误区提醒
- 在每次动画帧里反复获取
ctx或查询 DOM 元素——应提前缓存,避免重复开销。 - 在 draw 阶段做大量计算或 DOM 操作——保持 animate 函数轻量,耗时逻辑尽量移到更新阶段或用 Web Worker 分离。
- 忘记清空画布导致残影——除非是粒子拖尾等特殊效果,否则通常需要
clearRect或覆盖背景。 - 把 rAF 当成“每秒执行 60 次”的保证——实际帧率取决于设备性能和主线程负载,rAF 只是“尽力而为”,需做好降级兼容(如限制最低帧间隔)。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










