正确暂停游戏需中断 requestAnimationFrame 调用链并取消上一帧回调,同步清理事件监听、禁用 Canvas 交互、保存绘图状态,并将 isPaused 等状态作为游戏类实例属性管理。

暂停时如何正确冻结游戏主循环
核心是中断 requestAnimationFrame 调用链,而不是仅靠布尔标志位跳过逻辑——否则动画帧仍在消耗 CPU,音频可能继续播放,输入事件也未被拦截。
常见错误是只写 if (!isPaused) { update(); render(); },但 requestAnimationFrame 本身还在递归调用,导致“假暂停”。
- 暂停时必须显式取消上一帧的回调:
cancelAnimationFrame(animationId) - 恢复时重新调用
requestAnimationFrame(gameLoop)启动新帧,而非继续旧引用 - 若使用
setInterval或 Web Worker 做定时逻辑,也要同步clearInterval或发送暂停消息
状态变量该存在哪?全局、类属性还是 DOM data-*?
游戏运行状态(如 isPaused、gameState)应作为游戏主类的实例属性,而非全局变量或塞进 data-* 属性里。前者便于类型检查和 IDE 提示,后者易被误操作或 DOM 操作意外覆盖。
DOM 的 data-* 仅适合存 UI 相关的瞬态标记,比如 data-ui-state="paused" 用于 CSS 切换遮罩层可见性,不参与逻辑判断。
立即学习“前端免费学习笔记(深入)”;
- 状态变更必须同步触发副作用:修改
isPaused后,立刻调用updateUI()或切换 CSS 类 - 避免在多个函数里重复读取
document.body.dataset.uiState来决定行为,容易因 DOM 更新延迟导致状态不一致 - 如果用 Canvas,暂停时建议保存当前
canvas.getContext('2d').globalAlpha等绘图状态,防止恢复后渲染异常
键盘/触摸事件如何避免暂停期间误触发
暂停状态下仍响应 keydown 或 touchstart 是高频 Bug,尤其在移动端——用户点暂停按钮瞬间又滑动屏幕,导致恢复后角色突然移动。
不能只靠 if (isPaused) return 拦截事件处理函数,因为事件监听器本身已绑定,且某些浏览器会缓存 touch 事件。
- 暂停时调用
element.removeEventListener('keydown', handleKey),恢复时再addEventListener - 更稳妥的做法是在事件捕获阶段统一拦截:
document.addEventListener('keydown', preventIfPaused, true) - 对 Canvas 触摸操作,暂停后立即清空
touches缓存数组,并重置isDragging = false等中间状态
暂停界面遮罩层的 z-index 和 pointer-events 怎么设才不卡手
遮罩层不是加个 div 就完事。常见问题包括:点击穿透到下方 Canvas、遮罩无法响应点击、或遮罩出现时 Canvas 仍接受鼠标悬停事件。
关键不是层级高,而是交互意图明确:
- 遮罩层必须设
pointer-events: auto(默认是auto,但若父容器设了none就会被继承) - Canvas 元素在暂停时应加
style="pointer-events: none",彻底禁用其所有鼠标/触摸响应 - 遮罩内按钮需设
z-index: 1,但整个遮罩的z-index不必堆到 9999——只要高于 Canvas 的z-index即可,避免干扰其他页面模块
最易忽略的是 Canvas 的 tabindex 属性:若之前设为 0 使其可聚焦,暂停时记得 canvas.removeAttribute('tabindex'),否则键盘 Tab 仍能切进去触发 focus 事件。











