DOM 和 SVG 为什么第一个被淘汰?
很多人会问:为什么不用 DOM?为什么不用 SVG?
答案其实很残酷。
浏览器为了渲染 DOM 和 SVG 节点,底层维护了一套极其庞大的对象模型和布局引擎。每一次你在触控板上轻轻一划,哪怕只是让相机的世界坐标移动几个像素,都可能被迫引发大量节点的重排(Reflow)与重绘(Repaint)。
当节点树突破临界点,主线程被压垮,原本丝滑的缩放和平移就会掉进帧率泥潭。
无限画布的核心诉求只有一个:
无论同屏多少元素,都要稳定 60fps。
想达成这一点,你必须脱离浏览器的排版流,进入纯粹的"像素缓冲区"世界。
在当前的 Web 技术栈中,能承载这个目标的只有两条路:Canvas 2D 和 WebGL。
它们不是同一个问题的两种解法,而是两种截然不同的工程解法。
Canvas 2D:浏览器替你扛住复杂度
Canvas 2D 是绝大多数画布项目的起点——在线白板、流程图工具、轻量设计工具。
原因很简单:它太好用了。
画一个带边框的矩形:
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.strokeStyle = "#000000";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.roundRect(10, 10, 100, 100, [5]);
ctx.fill();
ctx.stroke();
想画曲线就调用 quadraticCurveTo,想写字就 fillText,想加阴影就配置 shadowBlur。
Canvas 2D 给你的不是 GPU 访问权,而是一套图形语义 API——你在表达"我要画什么",而不是"我要如何让 GPU 算出这些像素"。底层可能是 Skia、CoreGraphics 或 Direct2D,你不需要知道。
这就是它的价值。
但这背后有个隐藏代价:你把控制权交给了浏览器。
Canvas 2D 的指令调用是单向且串行的,绝大多数场景依赖 CPU 处理绘图指令。当你同屏几万个图形,每一帧都要重新执行所有 draw 指令。哪怕只是平移一个像素,也要把全部图形循环重绘一遍。
你没法告诉浏览器:
"把这批图形放到显存里,下次只改变换矩阵就行。"
Canvas 不给你这个能力。
所以当大量使用图层合成、全局阴影时,每帧的绘制时间很容易突破 16.6ms 的底线。
对于中等规模应用,Canvas 2D 性价比极高。但当你开始追求"Figma 级别"的体验时,它会成为瓶颈。
WebGL:你需要接管整个物理世界
跨入 WebGL 的那一刻,你会遇到一个残酷现实:
这个世界里没有矩形。只有三角形。
我们来做一个直观对比。
在 Canvas 2D 里画一个红色矩形:
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 100, 100);
2 行代码。
在 WebGL 里画同一个矩形:
// 1. 定义矩形四个顶点的坐标(拆分成两个三角形)
const vertices = new Float32Array([
-0.5,
0.5, // 左上
-0.5,
-0.5, // 左下
0.5,
0.5, // 右上
0.5,
-0.5, // 右下
]);
// 2. 将数据送入显存 Buffer 中
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 3. 编写 Vertex Shader 算坐标,编写 Fragment Shader 涂成红色
// ...(此处省略几十行 Shader 编译与变量绑定代码)...
// 4. 通知 GPU 绘制这两个三角形(Triangle Strip)
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
如果你想画一个"带圆角的 2 像素红框矩形"呢?
在 WebGL 中,连 roundRect 都没有。你需要用数学算法算出圆弧上的离散点,将边框转换成一个个细小的三角形,然后还要自己在着色器里处理抗锯齿(Anti-Aliasing)。
甚至连文字排版都是个难题。WebGL 本身不懂文字——你要么把文字先用 Canvas 渲染成图片,再贴到 WebGL 里作纹理(Texture),要么引入复杂的 SDF(有向距离场)算法让 GPU 渲染出不失真的文字轮廓。
复杂度是指数级增长的。
选择 WebGL,你不只是在换一个 API。你是在决定成为一个"引擎开发者"。
真正的问题不是性能
我后来想明白了一件事:
大多数人不是死在 Canvas 性能不够,而是死在过早复杂化。
如果你的同屏元素不超过 5000,Canvas 2D 够用。如果你连空间索引、脏矩形优化都没做,谈 WebGL 就是在逃避真正的问题。
但如果你的目标是做重型设计工具级产品——WebGL 是绕不过的坎。
区别在于:你是否愿意为"控制权"买单。
我们的选择:现实、克制
关于 Canvas 还是 WebGL,没有统一的正确答案。取决于你的产品对同屏元素量级的刚需,以及团队对底层图形基建的掌控力。
本系列不会从零手写 WebGL 引擎。
我们选择基于 Canvas 2D,但不会裸写绘制指令。
我们会引入 Konva.js 作为底层支撑。Konva 是一个基于 Canvas 2D 的场景图引擎,自带图元管理、分层渲染和像素级事件系统。我们将在此基础上,剥离前端框架(React/Vue)的边界,用纯 TypeScript 构建一个接近 Excalidraw 架构的轻量级无限画布。
这不是性能最极限的方案。
但它是当前阶段最诚实的解法。
真正的深水区:交互
无论 Canvas 还是 WebGL,它们本质上都只是一块"死板"的像素画布。
当你脱离 DOM,你就失去了节点树。
浏览器的 click 事件只能告诉你:用户点在了 { x: 500, y: 400 }。
但不会告诉你:这是第 7823 个图元。
悬停、拖拽、框选、事件冒泡——全部失效。
当画布变成一个没有结构的像素世界,点击本质上变成了一道几何题。
下一篇,我们用真实代码来解这道题:在没有 DOM 的"野生像素图"上,从零实现空间检测与事件分发系统。