生命游戏实现关键在于状态管理与计算逻辑:用fillrect高效绘制、环形边界处理邻居统计、requestanimationframe配合双缓冲确保同步更新,鼠标交互需用getboundingclientrect校准坐标。

Canvas 实现生命游戏的关键不在“能不能画”,而在于“状态怎么存、怎么算、怎么刷”——多数人卡在邻居统计边界处理或帧同步错乱上,不是 Canvas 本身难,是规则落地时细节没对齐。
如何用 fillRect() 高效绘制细胞网格
别用 strokeRect() 描边再填色,也别为每个细胞新建 DOM 元素。直接用 fillRect(x, y, width, height) 填充实心方块,性能差距能到 3–5 倍(尤其网格超 100×100 时)。
-
width和height应设为整数(如10),避免 Canvas 渲染器做 sub-pixel 插值导致模糊或闪烁 - 清空画布别用
ctx.clearRect(0, 0, canvas.width, canvas.height)反复调用——先用ctx.fillStyle = '#fff'+fillRect()覆盖更稳,尤其在高 DPI 屏幕下 - 如果要加网格线,单独用
strokeRect()或moveTo()/lineTo()绘制一次即可,不要每帧重画
邻居统计必须避开的三个边界陷阱
8 邻域计数写错,整个模拟就崩:死细胞不繁殖、活细胞不死亡、结构全漂移。核心问题是坐标越界后没做兜底。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 用“环形边界”(toroidal)最简单:
(i - 1 + rows) % rows替代i - 1,(j + 1) % cols替代j + 1,避免 if 判断分支,CPU 更友好 - 千万别直接写
matrix[i-1][j-1]—— JavaScript 里matrix[-1]是undefined,再取[j-1]就是Cannot read property 'j-1' of undefined - 如果坚持用“零填充边界”,必须在双重循环外提前判断
i === 0、i === rows - 1等情况,否则邻居数永远少算
为什么 requestAnimationFrame() 比 setInterval() 更靠谱
setInterval(..., 100) 看似每 100ms 更新一代,实际可能卡顿、跳帧、甚至两代合并计算——因为 JS 主线程忙时,定时器回调会堆积。
-
requestAnimationFrame()会把更新逻辑塞进浏览器渲染流水线,保证每帧最多执行一次,且与屏幕刷新率(通常是 60Hz)对齐 - 关键要配合“状态双缓冲”:用
currentGrid和nextGrid两个二维数组,每次在nextGrid上写新状态,算完再交换引用,避免边读边写导致的中间态污染 - 如果想控制演化速度(比如每 200ms 一帧),在
requestAnimationFrame()回调里加时间戳判断,而不是改帧率参数
鼠标交互时 getBoundingClientRect() 的坐标转换必须做对
Canvas 坐标系和页面坐标系默认不一致,直接用 event.clientX 点击位置去算格子索引,会偏移、错行、甚至点到画布外。
- 必须在事件回调里调用
canvas.getBoundingClientRect(),拿到left和top,再用Math.floor((x - rect.left) / cellSize)算列号,Math.floor((y - rect.top) / cellSize)算行号 - 别信
canvas.offsetLeft或canvas.offsetTop——它们不响应 CSS transform、zoom 或滚动条位置变化 - 移动端需额外处理
touches[0],且注意preventDefault()防止页面滚动干扰点击
最容易被忽略的是“同步更新”这个前提:所有细胞的新状态必须基于同一时刻的旧状态计算,不能边遍历边改 currentGrid。哪怕只漏掉一个 nextGrid[i][j] = ... 赋值,后续几代就会彻底失真——这不是 bug,是规则被破坏了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










