自研vsD3-svgjs
缺的不是「会不会调库」,是 文档模型、viewBox 相机、命中、命令栈。库能少写 enter/update 或少写 setAttribute,不能替你决定用户空间怎么映射到屏幕。不要把库当成「不用懂 viewBox」。
1. 怎么选
| 需求 | 倾向 |
|---|---|
| 节点编辑器、白板、图标系统、自己已有 scene | 自研 DOM(createElementNS + 你的文档) |
| 数据 → 几何的 join(比例尺、轴、enter/exit) | D3 当 join / 比例尺,渲染器仍是浏览器 SVG |
| 想少写 NS 和属性样板,产品不是数据可视化 | svg.js 可接受 |
| 新项目还在看 Snap.svg | 不要 |
export type SvgStack = "custom-dom" | "d3-join" | "svgjs";
export function chooseSvgStack(input: {
dataJoin: boolean;
existingScene: boolean;
editorChrome: boolean;
}): SvgStack {
if (input.existingScene || input.editorChrome) return "custom-dom";
if (input.dataJoin) return "d3-join";
return "svgjs";
}
editorChrome 指变换手柄、成组、吸附、序列化回自己的 schema。这些是文档命令,不是 D3 的 selection 生命周期。用 D3 硬做编辑器,最后会把 join 的 key 和撤销栈缠死。
2. D3 干什么、不干什么
D3 是 数据 join:selection.data(rows).join(enter, update, exit),外加比例尺、形状生成器(d3.line 吐 path d)。它 不是 SVG 渲染器。屏幕上的圆还是 <circle>,相机还是你的 viewBox,命中还是 getScreenCTM。
适用:图表、仪表、按数据增删系列。把比例尺当「数据空间 → 用户空间」的一层,用户空间仍进 SVG viewBox,不要把 d3.zoom 的 transform 和自己的相机再叠一套却两边都改 DOM。
不适用:
- 用 D3 当场景图去存业务对象。业务 id 在你的文档里;DOM 是投影。
- 指望 D3 处理 XSS。用户 SVG 消毒是 08,和 join 无关。
- 节点到了几千还每个点
append("circle")带滤镜。join 解决的是绑定,不是 节点成本。密集层滚到 Canvas / 聚合,见 图表。
interface Datum {
id: string;
x: number;
y: number;
}
/** join 只同步节点;坐标已是用户空间,不是 CSS。 */
export function syncCircles(
parent: SVGGElement,
rows: readonly Datum[],
ns: string,
): void {
const kids = [...parent.querySelectorAll("circle[data-id]")];
const byId = new Map(kids.map((el) => [el.getAttribute("data-id"), el]));
for (const row of rows) {
let el = byId.get(row.id);
if (!el) {
el = document.createElementNS(ns, "circle");
el.setAttribute("data-id", row.id);
parent.append(el);
}
el.setAttribute("cx", String(row.x));
el.setAttribute("cy", String(row.y));
byId.delete(row.id);
}
for (const leftover of byId.values()) leftover.remove();
}
这就是 D3 join 在干的事。你可以手写,也可以 d3.select(parent).selectAll("circle").data(rows, (d: Datum) => d.id)。几何与相机不在库里。
3. svg.js、以及不要 Snap.svg
svg.js
薄封装:SVG() 建树、动画、基础事件。适合插画、进度环、小编辑器原型。它不提供文档级撤销、也不懂你的 viewBox 相机。导出仍要自己 XMLSerializer 或走它的序列化并核对 xmlns。
Snap.svg
Adobe 时代的遗物,新项目不要选。生态停、和现代 Pointer Events / 模块工具链不匹配。存量维护可以包一层,不要当新架构的底。
Paper.js / Rough.js 解决的是几何或手绘风格,输出仍应是 path 进你的文档,不是第二套渲染循环。
4. 自研时不要重新发明的部分
即使不用 D3:
- viewport / viewBox /
preserveAspectRatio getScreenCTM命中- 命令栈撤销(不是像素快照)
- 用户 SVG 白名单
- 节点虚拟化
库内部画的仍是这棵 SVG 树。DPR、inline XSS、<img> 沙箱规则不变。
5. 失败形态
| 症状 | 原因 |
|---|---|
| 上了 D3 缩放后点不准 | 把 CSS 像素当了数据空间,或 zoom 和 viewBox 各改一次 |
| 撤销一步数据乱 | 把 D3 join 当了文档,没有自己的 command |
| Snap 新项目找不到维护 | 选了已退出主场的封装 |
| 以为用了 svg.js 就不用设 viewBox | 库没有取消用户空间 |
权威资料
核对日期:2026-08-26