用canvas画格子比div拼更靠谱,因其避免dom重排重绘、性能可控;推荐fillrect绘制、raf驱动、键盘标记方向后统一处理、旋转需偏移回退、清行须自底向上重建数组。

用 canvas 画格子比用 div 拼更靠谱
直接操作 DOM 节点(比如一堆 div 表示方块)在移动、旋转时卡顿明显,尤其在低配设备上。浏览器重排重绘开销大,帧率很难稳住 60fps。canvas 是位图绘制,所有方块状态由 JS 管理,只在每一帧清空再重绘,逻辑干净、性能可控。
实操建议:
- 用
canvas.getContext('2d')获取上下文,别用webgl——俄罗斯方块不需要三维或着色器 - 固定画布尺寸(如
width="300" height="600"),CSS 缩放会导致像素模糊,用devicePixelRatio做高清适配反而更复杂,初版先忽略 - 每个方块用
fillRect(x, y, size, size)绘制,size推荐设为 20 或 24(能整除画布宽高,避免小数坐标导致的抗锯齿毛边)
requestAnimationFrame 控制下落节奏,别用 setInterval
setInterval 时间不准,容易累积误差:比如设 1000ms 下落一格,但实际执行可能延迟 10–20ms,连下 10 次就慢了 100ms,手感变拖沓;更糟的是,页面切到后台时 setInterval 可能被节流甚至暂停,切回来时方块“瞬移”到底。
实操建议:
- 用
requestAnimationFrame驱动主循环,在每次回调里计算「从上次调用到现在过了多少毫秒」,再结合当前等级对应的下落间隔(如 Lv1=1000ms,Lv10=200ms)判断是否该下移 - 关键变量要存:上次时间戳
lastTime、累计未处理时间accumulated、目标间隔dropInterval - 键盘响应(左右、旋转)必须用
keydown事件监听,且加event.preventDefault()防止页面滚动;但不要在keydown里直接移动方块——只标记方向,等下一帧统一处理,避免输入过快导致跳格
旋转逻辑绕不开「中心点偏移」和「碰撞回退」
看似只是把方块数组转 90°,但真实问题在边界:比如 T 块贴右墙旋转,会直接穿出去;或者悬空旋转后底部悬空,下一帧直接掉下去。纯靠「旋转后检查是否越界」不够,得允许「贴墙旋转」——即检测失败时,尝试向左/右平移 1 格再试一次。
实操建议:
- 旋转函数别写死 4 种形态,用二维数组转置 + 水平翻转实现通用 90° 顺时针:例如
rotated[i][j] = original[n-1-j][i] - 旋转前保存原始坐标,旋转后遍历新形状所有格子,调用统一的
isValidPosition(x, y)检查是否在游戏区内且不与已落定方块重叠 - 若无效,按顺序尝试偏移
{x: -1, y: 0}、{x: 1, y: 0}、{x: -2, y: 0}……最多试 2 格,再不行就放弃旋转 - 注意:软降(按住↓加速)和硬降(空格直接落地)都必须触发碰撞检测,否则可能嵌入已堆叠的方块中
清除行后「上层方块下落」不能逐行挪动
如果检测到第 5 行满,就让第 1~4 行每个方块 y += 1,看似合理,但会出错:当多行同时消除(如四连消),第 3 行方块下落时可能覆盖第 4 行还没处理的数据,导致部分方块消失或错位。
实操建议:
- 用「自底向上」方式重建场地数组:创建新空数组,从最后一行开始扫描,遇到非空行就复制到新数组的对应位置,同时记录已复制行数;这样所有上方行自然「落下」,且无覆盖风险
- 清除行数决定得分,但别在清除瞬间立刻加分数——等所有行处理完再结算,否则 combo 判定(如连续清除)会漏算
- 已落定方块存在二维数组
board[y][x]中,值为颜色索引或null;动态下落中的方块(currentPiece)永远不写入board,只在它真正停稳后才合并进去
最难调的其实是「旋转+贴边+多行消除」三者叠加时的状态同步——稍有疏漏,方块就卡在半空或钻进地板。动手前先用纸画两轮坐标变化,比对着代码猜强得多。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











