接到 diagram-design 这个内部工具需求时我们团队正在给一批历史系统补架构文档。画图这件事看着简单真要沉淀下来其实很麻烦用通用绘图软件画完是张死图没法跟代码目录、部署清单关联用在线白板又太重评审时一页一页翻找很费劲。琢磨一圈之后我们决定自己做一个轻量的、以 JSON 为核心、面向技术文档的图设计器。这篇文章想把从数据模型到交互实现、再到导出对接的完整思路写出来给打算做类似画布工具、或者正在纠结选型的人一个参考。先说结论diagram-design 不是一个通用的绘图软件它只解决一件事——让技术团队能快速产出“能进文档、能被检索、能被版本管理”的图。它的核心不是画得好看而是把图画成数据。围绕这个目标从选型到实现都做了不少取舍下面挨个说。1. 需求收敛一张能“进文档”的图和一张“能看”的图差在哪很多画布类工具的需求来源是“我们想画漂亮的图”但我们这个项目的需求来源完全相反业务方反馈的是“文档里的图过几个月就看不懂了”。图本身是静态的看不懂不是因为美术差而是因为图和人之间没有任何结构化关联。于是我们把需求重新翻译了一遍落到具体功能上。1.1 从 12 条使用场景里提炼出的三个硬约束跟各组聊了一圈收集上来大概 12 类使用场景包括画部署架构、画调用链、画模块依赖、画排期图、画组织边界等等。去重归纳之后真正绕不开的只有三条硬约束图必须能序列化成纯文本格式方便进 Git 做 diff 和评审元素必须支持元数据挂载比如节点可以挂 URL、负责人、状态、版本号渲染结果要能导出成矢量图和位图兼容文档系统和演示场景。这三条直接决定了后续所有技术选型。比如第四条“画多漂亮”反而是最弱的约束我们干脆把配色、间距、对齐这些视觉规则做成了自动化的部分后面专门有一节讲这个。1.2 为什么没用现成的画布方案立项时第一反应肯定是看现成的开源画布库。市面上的方案大致分两类一类偏流程编排内置了泳道、连线、自动布局但这套模型是绑死的想挂自定义元数据很别扭另一类偏自由绘图底层是 SVG 或 Canvas灵活但序列化格式不统一不同库导出的 JSON 互换基本不可能。我们也调研过直接基于通用矢量编辑器的二次开发方案结果发现改造成本比从零做还高因为通用编辑器自带的行为太多比如复杂的样式面板、多选分组、对齐辅助这些功能对技术文档场景反而是噪音。最后选型结果是渲染层用 SVG模型层完全自研交互层自己写。当时测算下来最核心的节点拖拽、连线、缩放平移三块工作量大约两到三周换取的是完全可控的数据协议这个交换很值。2. 数据层先行节点、边、画布的三层协议很多工具失败在“先画界面再补数据模型”。diagram-design 反过来第一件事就是定协议。图本质上是一张有向图节点是实体边是关系画布是容器三者必须分开描述绝不能揉在一起。2.1 用 TypeScript 定义一份图数据协议type DiagramNode { id: string; type: system | module | resource | group; x: number; y: number; width: number; height: number; label: string; meta: Recordstring, unknown; }; type DiagramEdge { id: string; source: string; target: string; label?: string; style?: solid | dashed | dotted; }; type DiagramDocument { version: number; title: string; nodes: DiagramNode[]; edges: DiagramEdge[]; };这套协议看起来朴素但它把概念边界划得很清楚坐标、尺寸是纯展示信息meta 是业务信息边只描述拓扑关系。注意协议里没有 color、fontSize 这类样式字段这被我视为项目里最重要的一次取舍——样式应该由主题层注入而不是写死在模型里。2.2 序列化版本号与兼容策略协议定完紧接着设计了 version 字段。因为图数据一定会跨版本迭代没有版本号的序列化格式就是给自己埋雷。我们的策略是每次模型发生破坏性变更version 加一并在加载模块里写一个 migrate 函数列表从旧版本逐级升级到新版本。举个例子v1 的节点没有 width/height是自动按文字宽度计算的v2 改成显式尺寸后migrate 就遍历所有节点用缓存文字估算函数补上尺寸。这看起来是个小细节但是在实际使用中大有用处文档里嵌入的图数据可能是半年前生成的加载时必须保证打开即修复。版本化迁移的成本不高但能省掉后面大量兼容性排查时间。3. 渲染层选型与性能拆解SVG 底座上的三层优化渲染层的选择直接影响后面所有交互代码的写法。diagram-design 选择 SVG一开始不少人觉得奇怪毕竟大画布性能瓶颈在业界都默认“该用 Canvas”。但我们的真实使用场景里一张图平均 30 到 80 个节点极少数到 150 个节点这个量级下 SVG 的优势远大于劣势。3.1 为什么坚持 SVG 而不是 Canvas关键是两点第一SVG 元素天然是 DOM 的一部分可以挂事件和 CSS 类这对实现“节点的 hover 态、选中态、连线热区”非常友好写起来不用自己维护一份命中检测逻辑第二SVG 支持局部更新、独立样式、无障碍树团队后续要做数据标注和键盘导航时不用推翻重来。Canvas 适合“每次全量重绘但画面元素极多”的场景比如地图、大规模拓扑。我们要的不是 1 万节点的大图而是“团队协作时每个人都看得懂、改得动”的工程图SVG 的灵活性和可调试性价值更大。如果你也在做类似规模的项目我的经验是别被性能焦虑带偏节点数低于 200 的场景SVG 的维护成本远低于 Canvas。3.2 性能拆解视口裁剪、事件代理、脏标记即便 SVG也不能无脑把所有节点渲染出来。我们做了三层优化第一层是视口裁剪。维护一个当前可视区域矩形任何节点或边如果与视口无交集就不进入渲染列表。缩放拖拽过程中持续计算普通的架构图在 4 倍缩小时实际渲染的节点常常只有全量的 20%重绘压力小很多。第二层是事件代理。不在每个 SVG 节点上单独绑定监听器而是把监听器挂在画布根元素上通过>type Command { name: string; undo: () void; redo: () void; };这个设计的好处是撤销一个“批量粘贴 30 个节点”的操作不需要存 30 个节点的全量状态只需要记录粘贴时文档的节点数组变化即可。内存占用稳定而且命令栈天然支持分组、合并比如连续拖动同一节点 0.5 秒内的微调可以合并成一条命令。默认撤销步数我们设了 50 步文档场景完全够用。5. 视觉规则自动化让图天生就是“好看的”用 diagram-design 最多的同事不是设计师而是后端工程师。所以项目里一条铁律是不能用“手动对齐”来依赖用户的审美。我们做了两套自动化机制极大拉低了产出“难看图”的概率。5.1 网格吸附与对齐线画布默认开启 8px 网格吸附节点移动时坐标自动取整到 8 的倍数。这只是基础。更重要的是对齐线拖动节点时程序实时计算它和其他节点的左边界、中心、右边界之间的距离当差值小于 5px 时显示一条参考线并临时吸附。这个功能是纯几何计算没有任何魔法把可视区域里所有节点按 x 轴和 y 轴分别收集边界集合拖拽中的节点边界去这些集合里查找“最近距离小于阈值”的项即可。实现不难但从产品效果上看它直接让非设计背景的用户也能排出整齐的图。我后来复盘这个功能的性价比在整项目里排前三。5.2 树形布局与层级排序架构图有一定比例是天然的树形结构比如模块调用树、目录依赖树。针对这些场景我们内置了一个快速布局按钮选择一组节点指定一个根节点自动按广度优先分层同层节点纵坐标一致每个节点的横坐标按子节点数目均分。算法原理不复杂就是先按边的关系做一次拓扑排序再自底向上计算每棵子树的宽度最后分配坐标。但自动布局后必须保留用户的微调能力所以我们规定布局算法只写坐标不写尺寸、样式。自动排完、人工微调、再继续编辑这符合真实工作流机器先给一个不错的起点人只负责修正少量细节。6. 导出对接与版本化图从画布变成文档资产最后一步是把图变成文档里真正可用的资产。diagram-design 提供三种出口PNG、SVG、JSON它们对应完全不同的使用场景。6.1 PNG、SVG 与 JSON 三种出口的使用场景PNG 用于快速分享和演示直接放到微信聊天或者 slide 里谁都能看。导出时我们做了两倍像素缩放保证高清屏下不模糊。SVG 用于正式文档和二次编辑保留全部矢量信息文档站点引入后还能通过 CSS 覆盖颜色暗色主题下自动适配。JSON 才是这张图的“源文件”。它既能被重新拖入编辑器打开也能放进 Git 仓库参与代码评审还能被脚本读取做数据校验。我们有几条 CI 脚本会检查 JSON 里是否存在孤立节点、悬空边、重复 id不规范就发提醒。6.2 JSON 作为唯一事实源带来的协作红利这也是我觉得 diagram-design 和其他画图工具有本质区别的地方。因为 JSON 是唯一事实源图就和代码一样可以被 diff、被注释、被版本回滚。有一次同事在评审时发现某张架构图指向了一个已经下线的服务直接在评论里贴出 JSON 路径和错误节点 id负责人在五分钟内就改完并提交了新的图版本整件事全程没有截图、没有“你打开文件看看”这种低效沟通。另一个隐性红利是JSON 可以对接文档自动生成。我们从代码仓库里定时扫描部署清单自动生成一张资源拓扑图的 JSON再经过 layout 算法排版直接嵌入线上文档。这张图每天更新一次几乎零维护成本。做到这一步的时候我确定当初“把图画成数据”的决策是对的画布只是编辑器数据和接口才是资产。最后说一句个人体会。diagram-design 项目推进过程中我们反复被业务方追问“能不能加更多样式模板”“能不能做成多人实时协同”但这些都被搁置了。真正让大家天天在用的反而是网格吸附、命令撤销、JSON 导出这几个朴素功能。如果你也在做类似的画布工具我的建议是先把数据协议和导出链路夯实再去追求花哨的视觉效果——一张图的长期价值永远在于它能否被复用、被检索、被准确表达而不在于第一次打开时的惊艳。