Canvas渲染比div布局更稳,尤其在连按、旋转、消行动画时不会掉帧或错位;应仅用grid定义容器结构,方块绘制交由canvas或单个div+transform控制,配合requestAnimationFrame实现60fps稳定运行。

canvas 渲染比 div 布局更稳,尤其在连按、旋转、消行动画时不会掉帧或错位。纯 CSS 实现的俄罗斯方块(比如用 grid + position: absolute)能跑通基础逻辑,但一加速度控制或帧同步就容易失步——这不是写法错,是 DOM 更新机制本身有延迟。
用 grid 布局画游戏板,但别用 div 拼方块
很多人照着教程用 for 循环生成 200 个 div 表示格子,再给每个加 class="cell current" 来标出当前方块位置。这看似直观,实际会卡:
- 每次移动都要批量操作 DOM 节点,浏览器重排开销大
- 键盘连按(比如长按 ←)时,
keydown触发太快,但 DOM 更新没跟上,导致“跳格”或“穿墙” - 消行动画靠
transition+opacity或transform,但多个格子同时触发,CSS 动画时间难对齐
更可行的做法是:只用 grid 定义容器结构,所有方块绘制交给 canvas 或单个 div#game-area 内部绝对定位 + transform: translate() 控制。例如:
#game {
display: grid;
grid-template-columns: repeat(10, 30px);
grid-template-rows: repeat(20, 30px);
gap: 1px;
background: #222;
}
#game > .block {
width: 30px;
height: 30px;
position: absolute;
border-radius: 2px;
}
然后 JS 只更新 .block 的 style.left 和 style.top,不增删节点。
立即学习“前端免费学习笔记(深入)”;
requestAnimationFrame 替代 setInterval 控制下落
用 setInterval(update, 1000 / level) 是初学者常见写法,但它和屏幕刷新率不同步,容易出现“抖动”或“卡半帧”。尤其当用户切到其他标签页再切回来,setInterval 可能积压多轮调用,直接崩状态。
实现 Markdown↔ HTML 互转,支持导出 PDF,用于用户请求的文档格式转换。
-
requestAnimationFrame会自动对齐显示器刷新率(通常是 60fps),且页面不可见时暂停调用 - 必须手动计算时间差,不能假设每帧都是 16.6ms;比如下落逻辑要基于“累计流逝时间 ≥ 下落间隔”才执行,否则加速后会变慢
- 键盘响应仍走
keydown,但要把按键存入队列,每帧只处理一次,避免连按过载
关键代码片段:
let lastTime = 0;
function gameLoop(timestamp) {
const delta = timestamp - lastTime;
lastTime = timestamp;
<p>if (dropAccumulator >= dropInterval) {
moveDown();
dropAccumulator = 0;
} else {
dropAccumulator += delta;
}</p><p>draw(); // 清屏 + 重绘所有块
requestAnimationFrame(gameLoop);
}
CSS 动画只用于“消行”这类一次性反馈
方块移动、旋转、堆叠这些高频操作,别用 transition。但消行时的“闪烁→清除→下落”可以交由 CSS 完成,更轻量:
- 给即将消除的整行格子加 class
line-clear - 在 CSS 中定义:
.line-clear { animation: clear 0.3s ease; } - 动画结束后用 JS 批量清理数组并下移上方行,避免逐行
removeChild
注意:不要给每个格子单独加动画,否则 10 个格子触发 10 次重绘。统一用父容器或伪类控制。
颜色与形状映射必须用常量,别硬编码
看到有人在 JS 里写 if (shape === 'I') color = '#00f0f0',这种写法后期改色、加新方块、调试碰撞都痛苦。应该一开始就建好映射表:
const TETROMINOES = {
I: { shape: [[1,1,1,1]], color: '#00f0f0' },
O: { shape: [[1,1],[1,1]], color: '#f0f000' },
T: { shape: [[0,1,0],[1,1,1]], color: '#a000f0' },
// ……
};
<p>// 使用时直接取
const piece = TETROMINOES.I;
ctx.fillStyle = piece.color;
这样改颜色不用翻逻辑,加新方块只需扩对象,类型检查(TypeScript 下)也能立刻捕获拼写错误。
真正卡住人的从来不是“怎么画一个方块”,而是“怎么让 20 行 × 10 列的网格在 60fps 下稳定响应方向键、旋转、消行、计分全部逻辑”。DOM 渲染路径长、样式计算不可控,canvas 或极简 DOM + transform 是更现实的选择。那些看着“纯 HTML+CSS”的 demo,背后往往悄悄用了 canvas 或做了大量防抖/节流——这点容易被忽略。


















