核心是生成一棵无环连通的生成树,需用递归回溯或Prim算法构建唯一路径;用Uint8Array按位存储四面墙状态,先生成数据结构再统一绘图,避免阻塞与像素错位。

用 canvas 画迷宫,核心不是“画”,而是“生成有效路径”
直接调用 fillRect 画一堆方块不难,但那只是栅栏图,不是迷宫。真正可用的迷宫必须满足:有唯一入口、唯一出口、任意两点间存在且仅存在一条通路(即生成一棵生成树)。否则玩家会卡死、绕圈、或根本走不到终点。主流可靠方案是基于图论的递归回溯或 Prim 算法,不是随机挖洞。
recursiveBacktracking 是最易理解也最稳的生成方式
它模拟“人贴着墙走”的行为:从起点出发,随机选择一个未访问的邻格,打通墙壁,标记为已访问,递归进入;若四周都已访问,则回退。最终覆盖全部单元格,天然无环。
- 必须用二维布尔数组(如
visited[y][x])记录状态,不能只靠 canvas 像素颜色判断——后者慢且不可靠 - 每步“打通墙壁”实际是清空两个格子之间的共用边:比如从 (2,3) 向右走到 (2,4),就要把 x=3.5 这条竖线在 y∈[2,3] 区间擦掉
- 推荐单元格尺寸设为整数(如 20px),墙宽固定 2px,避免 canvas 像素对齐模糊导致视觉断连
- 别在递归里直接调用
ctx.fillRect——大量绘图操作会阻塞,应先生成完数据结构(如grid二维数组表示每个格子四面墙的布尔值),再统一绘制
用 Uint8Array 存墙状态比对象数组快 3 倍以上
一个 50×50 迷宫有 2500 个格子,每个格子需存上/右/下/左四面墙的状态。用 {top:true, right:false,...} 创建 2500 个对象,GC 压力大且内存碎片多。更高效的是用单个 Uint8Array,每位代表一堵墙:
const WALL_TOP = 0b0001; const WALL_RIGHT = 0b0010; const WALL_BOTTOM = 0b0100; const WALL_LEFT = 0b1000; // grid[y * width + x] 的值按位与即可查某面墙是否存在
生成完后遍历一次数组绘图,ctx.moveTo/ctx.lineTo 连线比反复 fillRect 墙体更轻量。
立即学习“前端免费学习笔记(深入)”;
导出为图片时 toDataURL 可能截断,优先用 toBlob
大尺寸迷宫(如 1000×1000px)调用 canvas.toDataURL('image/png') 在某些浏览器会返回空字符串或截断,因为 base64 字符串超长触发安全限制。正确做法是:
- 确保 canvas 宽高在设备像素比(
window.devicePixelRatio)下不过载,必要时缩放绘制 - 改用
canvas.toBlob(callback, 'image/png', 0.95),回调中创建URL.createObjectURL(blob)再赋给<a download> - 注意:
toBlob是异步的,别在它外面直接读返回值
迷宫算法本身不复杂,但 canvas 渲染链路上的像素对齐、内存模型、导出边界这些细节,才是上线后被用户截图发群问“为什么我的迷宫少了一堵墙”的真实原因。



















