从 Mermaid 到 WebGPU:一年出图踩过的四个坑
写博客一年多了,画了大概大几十张图。架构图、时序图、数据对比柱状图、TCP 拥塞窗口变化曲线。
然后有一天下班路上我突然意识到一件事:我每天都在用 Mermaid,但我完全不知道它背后是什么技术在画。
Mermaid 写一个 ```mermaid 代码块,保存,网页上就有了一张图。但它是怎么从文本变成 SVG 的?SVG 又是什么?为什么我用 Chart.js 画柱状图,用 ECharts 画折线图的叠加?如果我要画的不再是一张图,而是一个实时更新的仪表盘呢?
这些问题我回答不上来。于是花了几周摸了一遍前端出图的技术栈,从 Mermaid 的 dagre 布局引擎一路摸到 WebGL 着色器、WebGPU 的计算管线。
这篇文章不是结论——是翻车记录。四个坑,每个都让我对前端图形多理解了一层。
坑一:Mermaid 自动布局让我白忙了两天
写一篇关于 DeepFlow 的 eBPF 架构解析,需要一张比较复杂的架构图:数据从 Agent 采集,经过 Flow 和 Packet 两条路径,打到 Server 端做关联分析,再到数据存储和展示。大概十来个模块,有双向箭头、有分组。
Mermaid 两分钟写出来:
flowchart TD
A["Agent 采集"] --> B{"协议类型"}
B -->|"Flow"| C["Flow 处理"]
B -->|"Packet"| D["Packet 处理"]
C --> E["Server 端关联"]
D --> E
E --> F["ClickHouse"]
E --> G["Prometheus"]图能看,但布局不太对——几条线交叉在一起,Flow 和 Packet 两路应该是平行的,但 Mermaid 自动排成了一上一下。
接下来的两天,我做了非常愚蠢的事:试图通过 Mermaid 的各种配置项让布局变对。
加 ~~ 隐形边调顺序,调 graph 换成 flowchart 期望不一样的布局算法,甚至在节点名上加数字前缀来控制顺序。结果是——永远有另一条线交叉了。
后来在一个 D2 的讨论帖里看到一句话:“你试图用一个自动布局工具做手动布局的事。这不是工具的问题。”
我才意识到关键区别:
- Mermaid 是"文本→自动布局→SVG"。你交出布局控制权,换"写起来快"。
- Draw.io / Excalidraw 是"手动画→SVG"。你保留像素级控制权,但每张图都要手调。
- D2 / Structurizr 是"文本→规则布局→SVG"。你可以写布局约束(上下左右关系),比 Mermaid 控制力强,但比画图快。
我当时应该直接放弃在 Mermaid 里"修复"布局,换成 D2 写布局约束。或者干脆用 Draw.io 手动画一张。但我选了最错误的路径——用自动布局工具去做手动布局的事。
这个坑的教训我后来总结成一个分类:
| 你的需求 | 工具 | 控制权 | 速度 |
|---|---|---|---|
| 草图、快速分享 | Excalidraw | 完全手动 | 快 |
| 常规博客图 | Mermaid | 自动(不可调) | 极快 |
| 有结构要求的复杂图 | D2 / Structurizr | 规则控制 | 中 |
| 精确到像素 | Draw.io / Figma | 完全手动 | 慢 |
Mermaid 仍然是博客里默认的第一选择——毕竟 ```mermaid 写在 Markdown 里,别的地方做不到。但当你觉得"布局不对"的时候,先问自己一句:是我不接受自动布局的随机性,还是这个图的结构真的超出了 Mermaid 能处理的范围?
前者你要学会接受。后者你该换工具。
坑二:被 Chart.js 的"轻量"框住了视野
写国产大模型成本对比的文章时,需要一张简单的柱状图:横轴是模型名(GLM-5、Kimi K2.5、MiniMax M2.7),纵轴是 API 单价。六个数据点,要带 tooltip。
Chart.js 是最自然的选择。但画完之后,我觉得它不够好看——默认的样式偏朴素,柱状条的配色不够专业,tooltip 的光标样式也不对劲。
于是我开始想:“是不是该换 ECharts?ECharts 的图表更漂亮,动画也更丰富。”
差一点就换了。就差在 CDN 里把 Chart.js 的 script 换成 ECharts 的这一步。
但停下想了想:我就六根柱状条,换成 ECharts(gzip 360KB)的体积是 Chart.js(68KB)的 5 倍。就为了解决 tooltip 样式和颜色?
最后我做了 Chart.js 插件——十几行代码注册了一个 custom plugin,改了 tooltip 背景色、圆角、和标签格式。总代码量和用 ECharts 的 option 配置差不多,但体积是 68KB 还是 68KB。
这个坑表面上是"差点过度选型"——但实际上它暴露了一个更深的问题:“够用"的判断标准是什么?
我以前判断选型的逻辑是功能清单法——“ECharts 有 50 种图表类型,Chart.js 只有 8 种,所以 ECharts 更强”。但这个逻辑忽略了两个关键变量:
- 你需要的功能数量。不是"ECharts 有多少功能”,而是"Chart.js 能不能覆盖我这次要用到的功能"。对于一张柱状图,答案是 100% 能。
- “差一点不够"和"远超需求"之间的成本差。Chart.js 不够,加一个 plugin 多 2KB。选 ECharts,多 292KB。
这个成本差在你只用一张图的时候是 292KB。但如果你是数据密集型场景(仪表盘、实时监控),这个成本会被摊销——ECharts 的大数据采样、渐进渲染、50+ 图表类型都变得值得。
所以"够用"不是静态的,取决于你的使用频率和数量级。画一张图用 Chart.js,画一百张图用 ECharts,这个分界线很清晰。
但很多人踩的坑是我这个——只画了一张图,却用选仪表盘的逻辑在选型。
坑三:默认用 Canvas 做动画,选错了方向
写 TCP 拥塞控制系列文章的时候,想展示 Reno、CUBIC、BBR 三种算法的拥塞窗口随时间的变化。概念上就是几条曲线从左上角到右下角,像心电图一样渐进绘制出来。
第一反应:“动画嘛,当然用 Canvas。”
花了两天写了一个 Canvas 版本的实现。每帧清空画布,然后依次绘制三条曲线,用 requestAnimationFrame 驱动。效果还行,但有两个问题:
- 窗口缩放后 Canvas 重绘会闪一下(没有处理
devicePixelRatio变化) - 我实际上只是让三条线逐渐变长,并不是真的需要每帧重绘全部内容
后来我想:SVG 加 CSS 动画能不能做这件事?
答案是可以,而且代码更少:
| |
SVG 的 stroke-dasharray + stroke-dashoffset 属性配合 CSS animation,天然的"线条逐渐绘制"效果。而且浏览器在合成器线程处理 CSS 动画,不阻塞 JS 主线程。
这就引出了我这一年学到的最反直觉的一个事实:
很多人都认为 Canvas 一定比 SVG 快。但 SVG 在单元素局部更新方面更快。
一个简单的测试(20000 个节点,只改一个元素):
- SVG:只重绘那一个 DOM 节点,耗时 12ms
- Canvas:清空 20000 个图元再重绘全部,耗时 87ms
差别接近 7 倍。因为 Canvas 是位图——它没有"元素"的概念,画上去就是像素。你想要更新任何一点点东西,都得清空整张画布,把所有东西再画一遍。
所以我后来总结的口诀是:
不是数量决定用 SVG 还是 Canvas。是更新模式。
| 更新模式 | 适合的技术 |
|---|---|
| 偶尔改几个元素(点击高亮、悬停变色) | SVG(DOM 树,元素级操作) |
| 每帧全部重绘(游戏、实时视频流) | Canvas(位图,无 DOM 开销) |
| 中等频率更新(几百个点每秒刷新) | Canvas + 脏矩形优化 |
| 海量图元互不干涉(散点图 10 万+) | WebGL |
我的 TCP 曲线属于"偶尔改几个元素”——只是三条线的长度在变。用 Canvas 是杀鸡用牛刀,还杀歪了。
坑四:花了一周学 Three.js,然后发现博客不需要
第四个坑其实不是画图踩的,是学习路线踩的。
看了几个数据可视化大屏的案例,里面有 3D 地球、飞线图、旋转的柱状塔。当时第一反应是:“博客能不能也做这种东西?”
于是开始学 Three.js。场景、相机、渲染器、几何体、材质、光源……折腾了一周,搭了一个旋转的 3D 散点图,确实挺酷的。
然后我问了自己一个问题:我博客的读者真的需要在浏览器里旋转一个 3D 数据图吗?
答案是不需要。他们要看的是 TCP 怎么工作、eBPF 怎么采集数据、大模型成本怎么对比。3D 旋转是一个"看起来很酷"的功能,但对我博客的内容来说,它不解决任何问题。甚至可能让读者困惑——“为什么这个页面加载这么慢,还有个 3D 图在转?”
不恰当的比喻:Three.js 对于博客作者来说,就像买了一台 CNC 雕刻机来切生日蛋糕——能切,切得还很好,但你邻居用蛋糕刀 30 秒搞定了。
后来我又研究了一下 WebGPU,新标准,理论上 2026 年的浏览器支持度已经不错了(Chrome 113+、Firefox 141+、Safari 26+)。它比 WebGL 更进一步:原生支持计算着色器(Compute Shader),可以在浏览器里做 AI 推理。
真的很酷。但我博客不需要。
这让我意识到一件事:知道什么不需要去学,和知道该学什么一样重要。 技术选型的边界不是"这个技术能做多酷的事情",而是"我遇到的问题在哪个层面"。博客作者面对的问题 9 成在 Mermaid 和 Chart.js 这层,不到 1 成需要到 WebGL。
后来我给自己的原则:学到 Three.js/WebGL 层,不是用来用,而是用来知道自己正在哪一层——以防万一哪天真的需要时,知道往哪个方向找。
几个并不是坑,但值得知道的事
上面四个坑是按踩的顺序记录的。但摸完整个栈后,还有几个认知层面的收获,不是翻车,但对选型有帮助。
关于 D3.js 的常见误解
很多人说"Chart.js 不够强,所以我要学 D3.js"。这是对 D3 最深的误解。D3 不是"更强大的 Chart.js"——它是完全不同的东西。Chart.js 和 ECharts 是图表工厂(配置数据,出图),D3 是图表零件工具箱——你自己组装图表,而不是从工厂取成品。
选择 D3 不是因为"我要的功能 Chart.js 没有",而是因为"我要做的视觉效果没有现成的图表类型能表达"。你不能说"我要画一个柱状图"然后学 D3——用 Chart.js 五分钟搞定的事情,D3 可能要一小时。
SVG 比你以为的有用得多
很多人觉得 SVG 是用来"画矢量图标"的。但 SVG 的标准远比这个丰富——它有滤镜(feGaussianBlur 做模糊)、渐变(linearGradient/radialGradient)、蒙版(mask)、裁剪路径(clipPath)、动画(<animate> / <animateTransform>)。绝大部分"博客级别"的图形效果,SVG 原生就能做,不需要引入任何第三方库。
CSS 动画的覆盖范围被低估了
博客里 95% 的动效需求,CSS 动画可以覆盖。transition 做状态过渡,@keyframes 做持续动画,scroll-driven animations(CSS Scroll-Driven Animations API)做滚动触发动画——这是新的标准,Chrome 115+ 已支持,不需要任何 JS。在这个范围内的需求,引入 GSAP 或任何 JS 动画库都是多余的。
那我现在真正用的是什么?
不列清单,说使用场景:
- 写文章配图 → Mermaid。因为它在 Markdown 里,版本管理,且够用。
- 数据图表 → Chart.js。只会在"明确的多种图表类型叠加"或"数据量万级以上"时才考虑 ECharts。
- 动效 → CSS animation / transition 优先。碰到"需要精确时序编排"的场景才引入 GSAP。
- 复杂构图 → Draw.io。一年用两三次,但每次都是"Mermaid 搞不定"的场景。
- Three.js / WebGL / WebGPU → 不看不用,但不删书签。知道在什么地方能找到就行。
你说这算不算"选型"?好像不太像——它更像是对自己真实需求的诚实。这一年最大的收获不是学会了 Mermaid 背后是 SVG、Chart.js 在什么场景会撑不住,而是学会了问自己一句:
“这个问题真的需要这个工具来解决吗?”
大多数时候,答案是不需要。