canvas是flappy bird唯一可行方案,需用requestanimationframe驱动、ctx2d绘制、数值化状态管理、aabb碰撞检测,并分离update/render逻辑。

Canvas 画布是唯一可行的渲染路径
纯 HTML + CSS 做不了 Flappy Bird——没有帧动画控制、无法实时检测碰撞、DOM 操作太慢。必须用 <canvas></canvas>,配合 requestAnimationFrame 驱动游戏循环。Canvas 2D 上下文提供像素级绘制能力,是这类简单物理游戏的底线要求。
常见错误:试图用 div + transform: translateY 模拟下落,结果跳跃卡顿、碰撞检测失灵、重力响应延迟。这不是“够用”,而是根本不可靠。
- 初始化时务必用
const canvas = document.getElementById('game');+const ctx = canvas.getContext('2d'); - 别用
setInterval替代requestAnimationFrame,后者能对齐屏幕刷新率,避免掉帧 - Canvas 尺寸必须用 JS 显式设置
canvas.width和canvas.height,不能只靠 CSS 缩放,否则图像模糊、坐标错乱
小鸟状态必须用数值变量而非 CSS 类切换
小鸟的旋转角度、Y 位置、速度,本质是连续变化的数值。用 class="rotated" 或 style="transform: rotate(30deg)" 切换,既无法精确映射物理状态,又会因浏览器重排/重绘引入延迟和抖动。
正确做法是全程维护一个状态对象:const bird = { y: 200, velocity: 0, rotation: 0 };,每一帧根据物理规则更新它,再用 ctx.save() + ctx.translate() + ctx.rotate() 绘制。
- 重力加速度建议设为
0.5,点击跳跃初速度设为-10(负值向上),数值过大会导致操作不跟手 - 旋转角度不要直接绑定
velocity,用Math.min(Math.max(bird.velocity * 3, -30), 90)限幅,否则翻转过度失真 - 跳操作必须监听
click和keydown(空格键),但要防连点:在bird.velocity = -10后加个isJumping = true标志,松开后才重置
管道碰撞检测只需判断矩形相交,别碰 SVG 或 getBoundingClientRect
Flappy Bird 的管道是左右两块固定宽度的矩形(上管+下管),小鸟是带旋转的小矩形。用 getBoundingClientRect() 算 DOM 位置?不行——Canvas 是位图,没 DOM 节点。用 SVG?过度设计,且事件和性能都不如原生 Canvas。
真实做法:把小鸟当前包围盒(考虑旋转后的四角)投影到轴对齐矩形,再和每组管道的上下两个 { x, y, width, height } 做 AABB(Axis-Aligned Bounding Box)检测。简单、快、100% 可控。
- 小鸟包围盒可简化为:以
bird.x和bird.y为中心、宽高固定(如 34×24)、旋转影响仅用于视觉,碰撞仍用未旋转矩形(玩家感知不到误差) - 管道数据存成数组:
const pipes = [{ x: 300, topHeight: 120, bottomY: 280 }];,每帧移动pipe.x -= 2,移出屏幕就splice(0, 1) - 碰撞条件就是:
bird.x + 4 pipe.x && (bird.y pipe.bottomY)
requestAnimationFrame 循环里必须分离 update 和 render
新手常把所有逻辑塞进一个函数里:计算位置、画背景、画鸟、画管道、检测碰撞……一旦某帧绘制耗时略高(比如首次加载图片),requestAnimationFrame 就会掉帧,物理模拟失步,小鸟突然“瞬移”或穿管。
必须拆成两层:先 update() 更新所有数值状态(位置、速度、管道生成/销毁),再 render() 单纯调用 ctx.drawImage 和 ctx.fillRect。这样即使渲染慢,逻辑仍按恒定节奏推进。
-
update()里做:重力累加、键盘状态采样、管道移动、新管道生成(当最右管道x 时推入)、碰撞判定并设 <code>gameOver = true -
render()里只做:清屏(ctx.clearRect)、画背景、画管道、画小鸟(含旋转)、画分数(ctx.fillText) - 分数统计逻辑放在
update():当管道x从 > bird.x 变为 ≤ bird.x,且之前未计分,就score++并标记已计
Canvas 的坐标系、浮点精度、定时器漂移、图片加载时机——这些细节不写几十行注释压不住。真正卡住人的从来不是“怎么画一只鸟”,而是第 37 帧时 bird.y 算出来是 NaN,或者管道间隙刚好比小鸟高 0.3 像素却判为碰撞。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











