用 requestanimationframe 替代 settimeout/setinterval 可实现与屏幕刷新率同步的高性能 canvas 动画,避免丢帧、卡顿和资源浪费;它由浏览器统一调度,支持自动降频或暂停,且需结合时间差(deltatime)实现匀速运动。

用 requestAnimationFrame(简称 rAF)替代 setTimeout 或 setInterval 是实现高性能 Canvas 动画的关键。它让浏览器决定最佳绘制时机,与屏幕刷新率同步(通常是 60fps),避免丢帧、卡顿和过度渲染。
为什么 rAF 比定时器更合适
rAF 不是“每 16ms 调一次”,而是由浏览器统一调度:在下一帧准备就绪时才执行回调;如果标签页不可见、动画暂停或系统负载高,它会自动降频甚至暂停,节省 CPU 和电量。而 setInterval(16) 强行触发,可能造成绘制堆积、掉帧,甚至在后台持续运行浪费资源。
基础用法:一个标准的动画循环
核心是递归调用 rAF,并在每次回调中更新状态、清空画布、重绘:
let animationId;
<p>function animate() {
// 1. 更新逻辑(位置、角度、速度等)
ball.x += ball.vx;
ball.y += ball.vy;</p><p>// 2. 清空画布(按需:全清 / 局部擦除 / 覆盖重绘)
ctx.clearRect(0, 0, canvas.width, canvas.height);</p><p>// 3. 绘制当前帧
ctx.beginPath();
ctx.arc(ball.x, ball.y, 10, 0, Math.PI * 2);
ctx.fill();</p><p>// 4. 请求下一帧(必须放在最后!)
animationId = requestAnimationFrame(animate);
}</p><p>// 启动动画
animationId = requestAnimationFrame(animate);</p><p>// 停止动画(例如页面隐藏、用户暂停时)
// cancelAnimationFrame(animationId);</p>处理时间差,实现匀速运动
rAF 的实际间隔不恒定(可能 14ms 或 17ms)。若直接用固定增量(如 x += 2),设备性能差异会导致快慢不一。应基于时间差计算位移:
- 在首次调用时记录
startTime或上一帧时间戳 - 每次回调接收浏览器传入的
timestamp(DOMHighResTimeStamp,单位毫秒) - 用当前时间减去上次时间,得到
deltaTime(毫秒),再换算为秒参与物理计算
let lastTime = 0;
const speed = 100; // 像素/秒
<p>function animate(timestamp) {
if (!lastTime) lastTime = timestamp;
const deltaTime = (timestamp - lastTime) / 1000; // 转为秒
lastTime = timestamp;</p><p>// 位移 = 速度 × 时间(单位一致!)
ball.x += speed * deltaTime;</p><p>ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(ball.x, ball.y, 10, 0, Math.PI * 2);
ctx.fill();</p><p>requestAnimationFrame(animate); // 无需存 ID,除非要停止
}
requestAnimationFrame(animate);</p>实用建议与注意事项
- 不要混用 rAF 和 setInterval:二者调度机制冲突,易引发竞态或重复绘制
-
清理资源:页面卸载或切换标签前调用
cancelAnimationFrame(id),防止内存泄漏 -
离屏 canvas 优化:复杂绘制可先画到离屏 canvas,再一次性
drawImage到主 canvas,减少上下文切换开销 -
节流交互响应:鼠标/触摸事件中避免直接调用
requestAnimationFrame多次,可用标志位确保单帧只更新一次
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











