requestAnimationFrame是网页游戏循环的首选方案,因其能与浏览器刷新率同步、避免卡顿掉帧、后台自动暂停;而setInterval因定时不精准易导致撕裂、不同步和耗电。

网页游戏开发中,用 requestAnimationFrame(简称 rAF)实现游戏循环,核心是让渲染与浏览器刷新率同步,避免卡顿、掉帧和时间漂移。它比 setTimeout 或 setInterval 更精准、更节能,是现代 Web 游戏循环的事实标准。
为什么不用 setInterval?
setInterval 按固定毫秒数触发,但浏览器实际刷新频率受硬件、标签页可见性、系统负载等影响,并不严格等于 60fps(约 16.67ms/帧)。强行“定时”会导致:画面撕裂、逻辑更新不同步、后台标签页仍耗电。而 requestAnimationFrame 由浏览器调度,在下一次重绘前执行回调,天然对齐屏幕刷新节奏,且在页面不可见时自动暂停。
基础游戏循环结构
一个典型的 rAF 游戏循环包含三部分:状态更新(update)、碰撞检测、渲染(render)。关键在于区分「逻辑帧」和「渲染帧」——理想情况是逻辑以固定步长(如 1/60 秒)运行,渲染尽可能跟上屏幕刷新。
- 用
performance.now()获取高精度时间戳,计算上一帧到当前帧的经过时间(deltaTime) - 累积 deltaTime,当达到目标帧间隔(如 16.67ms),执行一次逻辑更新,并减去该间隔
- 每次 rAF 回调都执行渲染,无论逻辑是否更新;这样动画更流畅
- 用递归调用
requestAnimationFrame(loop)维持循环,不要用 while 或 for
带固定逻辑步长的稳健循环示例
以下代码实现了「插值渲染 + 固定逻辑更新」,兼顾稳定性和流畅感:
let lastTime = 0;<br>
const FRAME_TIME = 1000 / 60; // 约 16.67ms<br>
let accumulator = 0;<br><br>
function update(deltaTime) {<br>
// 这里处理输入、物理、AI、状态变更等逻辑<br>
player.x += player.vx * deltaTime;<br>
checkCollisions();<br>
}<br><br>
function render(interpolation) {<br>
// 使用插值位置绘制,减少视觉抖动<br>
const x = player.x + (player.xNext - player.x) * interpolation;<br>
ctx.clearRect(0, 0, canvas.width, canvas.height);<br>
ctx.fillRect(x, player.y, 20, 20);<br>
}<br><br>
function loop(timestamp) {<br>
if (!lastTime) lastTime = timestamp;<br>
const deltaTime = timestamp - lastTime;<br>
lastTime = timestamp;<br><br>
accumulator += deltaTime;<br><br>
// 固定步长更新逻辑,防止因帧率波动导致物理失真<br>
while (accumulator >= FRAME_TIME) {<br>
update(FRAME_TIME);<br>
accumulator -= FRAME_TIME;<br>
}<br><br>
// 插值系数:用于平滑渲染(0=上一逻辑帧,1=下一逻辑帧)<br>
const interpolation = Math.min(accumulator / FRAME_TIME, 1);<br>
render(interpolation);<br><br>
requestAnimationFrame(loop);<br>
}<br><br>
requestAnimationFrame(loop);
注意事项和常见陷阱
- 别在 rAF 回调里做重操作:如大量 DOM 查询、复杂计算、未优化的图像处理——会阻塞渲染,直接掉帧
-
注意 canvas 清屏时机:在
render开头清屏,避免残留残影;若用离屏 canvas 或图层分离,需按需清 -
输入响应要低延迟:键盘/鼠标事件应立即记录状态(如
keys.left = true),不要等到下一帧才读取 -
暂停与恢复控制:监听
document.hidden,页面隐藏时停止 rAF;切回前台时重置lastTime防止大 deltaTime 突变



















