主题
02 · Canvas + requestAnimationFrame:最小可玩循环
目标:用 Vite + TypeScript 起一个固定尺寸画布,用
requestAnimationFrame跑通「清屏 → 画 → 再约下一帧」,并引入dt(帧间隔)。
官方:Canvas API · requestAnimationFrame
1. 为什么是 rAF,不是 setInterval
| 方式 | 问题 |
|---|---|
setInterval(16) | 与显示器刷新不同步;后台标签仍猛跑;掉帧时堆叠难控 |
requestAnimationFrame | 跟屏幕刷新对齐;页不可见时浏览器会节流/暂停 |
前端里你用 rAF 做动画;游戏里它就是 渲染心跳。逻辑怎么跟心跳解耦,见 03。
2. 最小工程(本机敲一遍)
在任意练习目录:
bash
pnpm create vite arcade-lab --template vanilla-ts
cd arcade-lab
pnpm install
pnpm dev1
2
3
4
2
3
4
index.html 里放画布:
html
<canvas id="game" width="480" height="640"></canvas>1
src/main.ts:
ts
const canvas = document.querySelector<HTMLCanvasElement>('#game')!
const ctx = canvas.getContext('2d')!
let last = performance.now()
function frame(now: number) {
const dt = (now - last) / 1000 // 秒
last = now
// 1) 清屏
ctx.fillStyle = '#0b1220'
ctx.fillRect(0, 0, canvas.width, canvas.height)
// 2) 画点东西(先证明循环在转)
ctx.fillStyle = '#7dd3fc'
const x = (now / 10) % canvas.width
ctx.fillRect(x, canvas.height / 2 - 8, 16, 16)
// 3) 约下一帧
requestAnimationFrame(frame)
}
requestAnimationFrame(frame)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
应看到方块水平扫过。控制台可临时 console.log(dt.toFixed(3)) 看帧间隔(约 0.016 ≈ 60Hz)。
3. 心智模型:一帧里发生什么
text
rAF 回调
├─ 算 dt(距上一帧多久)
├─ update(dt) ← 改位置、生成子弹……(本篇先极简)
└─ render() ← 只负责画当前状态1
2
3
4
2
3
4
对照前端:更像「每帧强制重绘的 Pinia 快照」,而不是 DOM diff。
约定(本系列):
- 逻辑用世界坐标(像素即可,阶段一够用)
- 渲染只读状态,不在
fillRect里偷偷改血量
4. 固定画布 vs 自适应
阶段一用 固定 480×640(竖版街机感),避免一上来就纠结 DPR / 响应式。
高清屏模糊、devicePixelRatio 缩放见 前端进阶 · Canvas;游戏篇先保证逻辑正确。
5. 常见坑
| 坑 | 处理 |
|---|---|
| 忘记清屏 | 残影拖尾;每帧先 fillRect 整画布或 clearRect |
把 dt 当毫秒又当秒 | 统一用秒:(now - last) / 1000 |
在循环里 addEventListener | 监听器爆炸;输入监听放循环外(见 04) |
getContext('2d') 为 null | 检查 canvas 是否在 DOM、选择器是否写对 |
验收清单
- [ ]
pnpm dev能打开带 canvas 的页面 - [ ] 方块持续移动,不是只画一帧
- [ ] 能口头解释
dt是什么、单位是秒 - [ ] 代码结构能分出「清屏 / 画 / rAF」三步
官方入口
- https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial
- https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
