requestanimationframe 实现游戏循环的核心是同步屏幕刷新率以避免卡顿,通过记录时间戳计算 delta time 保证逻辑平滑,支持渲染与逻辑帧率解耦,并需配合 cancelanimationframe 实现暂停与恢复。

用 requestAnimationFrame 实现游戏循环,核心是让渲染节奏与屏幕刷新率同步,避免卡顿、跳帧和掉帧。它不是“手动控制帧率”,而是“顺应浏览器的绘制节拍”——系统会在下一帧准备就绪时调用你的回调,天然支持 60fps(或更高刷新率设备下的对应帧率)。
基础结构:一个干净的循环骨架
最简但可扩展的游戏循环长这样:
- 每次回调里先计算自上一帧以来经过的时间(delta time),用于物理、动画等时间敏感逻辑
- 执行游戏更新(update):处理输入、AI、碰撞、状态变化等
- 执行渲染(render):把当前状态画到 canvas 或 DOM 上
- 最后再次调用
requestAnimationFrame,形成连续循环
注意:不要用 setTimeout 或 setInterval 模拟帧率,它们无法对齐显示器刷新,还可能被后台标签页节流。
必须处理 delta time:让运动真正平滑
浏览器不保证每帧间隔严格相等(尤其在性能波动、页面切后台再切回时),所以不能假设“每帧就是 16.67ms”。正确做法是记录上一帧时间戳,算出本次 delta:
let lastTime = 0;
function gameLoop(timestamp) {
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
<p>update(deltaTime); // 传入毫秒级时间差
render();</p><p>requestAnimationFrame(gameLoop);
}</p>
比如角色移动速度设为 200px/s,那每帧应走 200 * deltaTime / 1000 像素——这样无论帧率高低,单位时间内位移都一致。
控制逻辑帧率 vs 渲染帧率(可选但实用)
有些逻辑(如物理模拟)对时间精度要求高,频繁更新反而引入误差。可以将更新和渲染解耦:
- 保持
requestAnimationFrame驱动渲染(即每帧都 render) - 但只在累积足够 delta 后才执行一次 update(例如固定 60fps 更新,即每 ~16.67ms 一次)
- 剩余未消耗的 delta 留给下一帧,避免“时间丢失”
这种“固定 timestep + 可变渲染”的模式在 Box2D、Unity 等引擎中很常见,能大幅提升物理稳定性。
及时清理与暂停机制
用户切换标签页、弹出模态框、或游戏进入暂停状态时,应主动停止循环:
- 保存当前
requestAnimationFrame的返回值(一个数字 ID) - 调用
cancelAnimationFrame(id)中断下一次调用 - 恢复时重新发起
requestAnimationFrame即可
别依赖浏览器自动节流——有些行为(比如音频播放、WebSocket 心跳)仍需你主动管理,避免资源浪费或状态错乱。











