纯HTML+SVG架构图工具:出版级矢量图的工程实践
2026/9/16 3:00:38 网站建设 项目流程

1. 项目概述:为什么一个纯HTML+SVG的架构图工具,能引发设计师和工程师集体转发?

最近在GitHub trending榜上刷到一个叫diagram-design的仓库,标题写着“告别粗糙架构图”,我第一反应是——又一个画图工具?但点进去后,连续看了三遍README,又扒了源码结构,最后在本地跑通demo时,手有点抖。不是因为技术多炫酷,而是它用最朴素的Web原生技术,精准戳中了我们日常协作中最痛的那个点:一张图,要同时让架构师信服逻辑严谨、让前端确认交互路径、让UI设计师认可视觉质感,还要让产品经理一眼看懂业务流向。这事儿过去十年里,要么靠Visio拖拽凑合,要么用draw.io导出再PS精修,要么直接扔给设计师重做——成本高、版本乱、改起来像考古。

diagram-design不搞复杂渲染引擎,不依赖Node.js服务端,甚至不打包构建。它就一个index.html文件,里面全是标准HTML标签 + 原生SVG元素 + 纯CSS控制样式 + 少量ES6 JavaScript逻辑。没有React/Vue框架包袱,没有Webpack配置地狱,没有npm install半小时还在下载依赖。你把它丢进任意静态服务器(甚至双击打开),就能立刻编辑、缩放、导出高清SVG/PNG。更关键的是,它生成的SVG不是位图截图,而是语义化、可访问、可编程、可嵌入文档的矢量图——这意味着你可以把架构图直接贴进Confluence页面,用CSS统一换主题色;可以给每个模块加aria-label供屏幕阅读器识别;可以在CI流程里用Puppeteer自动截取部署前后的对比图;甚至能用Python脚本批量解析SVG里的<g id="service-auth">节点,自动生成微服务健康检查报告。

我试过用它重绘公司核心支付系统的三层架构图:网关层、业务中台、数据底座。原来用draw.io做的版本,导出PNG后放大200%就糊了,发给移动端团队看不清接口调用箭头方向;而diagram-design生成的SVG,在Figma里放大到400%依然锐利,设计师直接拖进设计稿当参考基准线。更意外的是,我把SVG代码复制进邮件HTML模板,收件人用Outlook打开,图照样清晰显示——这点连很多付费SaaS工具都做不到。它不追求“全能”,但把“架构图作为沟通媒介”这件事,做到了极致克制与极致可用。如果你也受够了画图工具导出失真、协作版本混乱、嵌入文档变形、无法自动化集成,那这个项目值得你花30分钟真正吃透它怎么工作。

2. 核心设计思路拆解:为什么放弃Canvas/WebGL,死磕原生SVG?

2.1 不是技术保守,而是场景倒逼选择

很多人看到“纯HTML+SVG”第一反应是:太老派了吧?现在不都上Canvas渲染、WebGL加速、Three.js做3D可视化了吗?但diagram-design的作者在issue里明确写过一句话:“架构图不是游戏画面,它不需要每秒60帧的动态渲染,它需要的是可读性、可维护性、可追溯性”。这句话直击本质。我们来拆解真实协作场景中的硬需求:

  • 可读性:架构图里一个矩形框代表“订单服务”,字体必须清晰可辨,哪怕打印在A4纸上;箭头上的文字“HTTP/2”不能因抗锯齿模糊;连线弯曲度要符合UML规范,不能为了性能牺牲语义精度。
  • 可维护性:当“用户中心”模块拆分为“认证服务”和“资料服务”时,工程师要能直接在HTML里找到<g id="user-center">,删掉旧节点,插入两个新<rect>并更新连线<path d="M100,200 Q150,150 200,200">——而不是打开GUI界面点选、拖拽、右键导出、再上传。
  • 可追溯性:Git diff要能看清哪一行SVG代码改了——比如把fill="#4CAF50"改成fill="#2196F3",代表从“已上线”状态切换为“灰度中”。Canvas渲染出的base64图片或WebGL纹理,Git根本没法做文本比对。

SVG原生支持这些能力:它是XML格式,可被任何文本编辑器打开;每个元素有明确ID和class,可被CSS精准控制;<text>标签支持xml:space="preserve"保留换行空格;<defs>里定义的渐变/滤镜可全局复用;<use href="#icon-db">实现图标复用降低体积。而Canvas是位图缓冲区,所有绘制操作都是命令式调用,一旦ctx.fillRect()执行完,像素就固化了,想改颜色得重绘整块区域;WebGL更底层,调试一个着色器错误可能耗掉半天。

2.2 HTML容器层:不只是外壳,而是语义锚点

diagram-design没把所有东西塞进<svg>里,而是用标准HTML结构包裹:

<div class="diagram-container"> <header class="diagram-header"> <h1>支付系统架构图</h1> <p class="version-tag">v2.3.1 · 2024-06-15</p> </header> <div class="diagram-body"> <svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg"> <!-- 所有图形元素 --> </svg> </div> <footer class="diagram-footer"> <button onclick="exportSVG()">导出SVG</button> <button onclick="copyToClipboard()">复制代码</button> </footer> </div>

这个看似简单的结构,实则暗藏玄机:

  • diagram-container作为整体尺寸控制锚点,配合CSSmax-width: 100vw; overflow-x: auto;实现响应式缩放,手机上看自动横向滚动,桌面端可全屏查看;
  • diagram-headerdiagram-footer不是装饰,而是元信息载体:标题用<h1>保证SEO和无障碍阅读;版本号用<p>便于CI脚本正则提取;按钮绑定JS事件,避免内联onclick污染SVG纯净性;
  • diagram-body里的<svg>设置viewBox而非固定宽高,确保缩放时比例不变形——这是SVG区别于Canvas的核心优势,Canvas需手动监听resize事件重设canvas.width/height并重绘全部内容。

我实测过,把这段HTML丢进Hexo博客的Markdown文章里,用{% raw %}...{% endraw %}包裹,渲染后图完全正常,且支持博客主题CSS覆盖(比如把.diagram-header h1改成深蓝色)。而Canvas方案必须引入额外JS库,且常因博客主题禁用<script>标签导致白屏。

2.3 SVG图元层:用最少的标签,表达最丰富的语义

diagram-design的SVG部分极度克制,只用5类基础标签:

标签典型用途关键属性示例为什么不用替代方案
<rect>服务模块、数据库、网关等矩形组件x="100" y="200" width="180" height="80" rx="8" fill="#E3F2FD" stroke="#2196F3" stroke-width="2"Canvas需计算4个点坐标再beginPath()+rect()+fill();SVG一行搞定,且rx圆角天然抗锯齿
<circle>节点标识、状态指示灯cx="500" cy="300" r="12" fill="#FF5252"Canvas画圆需arc()方法,参数多易错;SVGr属性直观,且<circle>天生支持<animate>做心跳动效
<path>弯曲连线、UML关联线d="M200,250 C250,200 350,200 400,250"Canvas贝塞尔曲线需bezierCurveTo()三次调用;SVG单个d属性描述完整路径,Git diff可读性强
<text>模块名称、接口协议、备注说明x="190" y="245" font-size="14" dominant-baseline="middle" text-anchor="middle"Canvas文本定位需手动计算基线偏移;SVGdominant-baselinetext-anchor精准控制对齐,支持<tspan>换行
<g>逻辑分组、图层管理<g id="auth-layer" class="layer">...</g>Canvas无原生分组概念,需用数组管理对象;SVG<g>可整体transform缩放/平移,且ID可被CSS/JS直接操作

特别值得注意的是<path>d属性。diagram-design没用D3.js的d3.line()生成路径,而是手写贝塞尔曲线指令。比如一条从“API网关”到“订单服务”的带标注箭头:

<!-- 连线主体 --> <path d="M150,180 C180,150 220,150 250,180" stroke="#78909C" stroke-width="2" fill="none" marker-end="url(#arrowhead)"/> <!-- 箭头定义 --> <defs> <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"> <polygon points="0 0, 10 3.5, 0 7" fill="#78909C"/> </marker> </defs> <!-- 接口标注 --> <text x="200" y="145" font-size="12" text-anchor="middle" fill="#546E7A"> <tspan>HTTPS</tspan> </text>

这种写法看似原始,但好处巨大:

  • d="M150,180 C180,150 220,150 250,180"中的控制点坐标,直接对应设计稿里的锚点位置,UI设计师给的PSD标注值(如“控制点距起点水平偏移30px”)可直接填入;
  • marker-end引用预定义箭头,修改全局箭头样式只需改<defs>里一处,无需遍历所有<path>
  • <tspan>支持多行文本,比如把“HTTPS/REST”分成两行,dy="1.2em"即可控制行距,Canvas需拆成两次fillText()调用。

3. 核心细节解析与实操要点:如何让SVG架构图真正“出版级”?

3.1 颜色系统:不是随便挑色,而是建立可扩展的语义色板

diagram-design的CSS里没有#ff0000这类魔法数字,而是定义了一套基于Material Design色阶的CSS变量:

:root { --color-service: #E3F2FD; /* 服务模块背景 */ --color-service-border: #2196F3; /* 服务边框 */ --color-db: #F3E5F5; /* 数据库背景 */ --color-db-border: #9C27B0; /* 数据库边框 */ --color-external: #FFF3CD; /* 外部系统背景 */ --color-external-border: #FF9800; /* 外部系统边框 */ --color-line: #78909C; /* 连线颜色 */ --color-label: #546E7A; /* 文字颜色 */ }

这套色板不是凭空而来,而是严格对应架构图通用语义:

  • 蓝色系(#2196F3):代表内部可控服务,符合“信任、稳定”心理暗示;
  • 紫色系(#9C27B0):代表持久化存储,紫色在色彩心理学中关联“深度、可靠”;
  • 橙色系(#FF9800):代表外部依赖(如微信支付SDK、短信平台),橙色传递“注意、边界”信号;
  • 灰色系(#78909C):作为中性连线色,避免干扰主视觉,且在彩色背景下仍保持足够对比度。

提示:实际使用时,我建议在<style>标签里追加媒体查询适配暗色模式:

@media (prefers-color-scheme: dark) { :root { --color-service: #1E88E5; --color-service-border: #42A5F5; --color-db: #4A148C; --color-db-border: #7E57C2; } }

这样设计师在Dark Mode下看图,颜色依然协调,无需额外导出暗色版本。

3.2 字体与排版:让文字成为架构图的可信度背书

架构图里文字大小、行高、字重,直接影响专业感。diagram-design强制使用系统安全字体栈:

.diagram-text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.4; font-weight: 500; }

为什么不用Google Fonts或自定义woff?两点硬约束:

  • 离线可用性:客户现场演示时网络可能受限,Web Font加载失败会导致文字回退到默认字体(如Windows的Times New Roman),破坏设计一致性;
  • 渲染性能:SVG里每个<text>元素都要触发字体度量计算,Web Font需额外HTTP请求+解析,首屏渲染延迟明显。

更关键的是排版细节处理:

  • dominant-baseline="middle"确保文字垂直居中,避免Canvas里手动计算y + fontSize * 0.35的误差;
  • text-anchor="middle"水平居中,配合x坐标精准落在模块中心;
  • 对长文本(如“用户行为分析与实时推荐引擎”)启用textLengthlengthAdjust属性强制等宽压缩:
<text x="300" y="400" text-anchor="middle" font-size="12" textLength="160" lengthAdjust="spacingAndGlyphs"> 用户行为分析与实时推荐引擎 </text>

这样即使文字超长,也不会溢出矩形框,且字符间距均匀,比CSS的overflow: hiddentext-overflow: ellipsis更符合出版级要求。

3.3 交互增强:不靠框架,用原生SVG事件实现专业体验

diagram-design的交互极简但精准:

  • 悬停高亮:CSS:hover直接作用于<rect>,改变fillstroke,无需JS监听;
  • 点击聚焦:为每个<g>添加tabindex="0",支持键盘Tab导航,按Enter键触发focus事件;
  • 缩放平移<svg>外层用<div class="zoom-container">包裹,CSStransform: scale(1.5)+overflow: hidden实现硬件加速缩放;
  • 导出逻辑exportSVG()函数不调用第三方库,而是直接序列化<svg>的outerHTML:
function exportSVG() { const svg = document.querySelector('svg'); const serializer = new XMLSerializer(); const svgString = serializer.serializeToString(svg); // 添加XML声明和DOCTYPE,确保浏览器可直接打开 const fullString = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n${svgString}`; const blob = new Blob([fullString], {type: 'image/svg+xml'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'architecture-diagram.svg'; a.click(); URL.revokeObjectURL(url); }

这段代码亮点在于:

  • XMLSerializer是浏览器原生API,兼容IE11+,无需引入xmlserializer包;
  • 手动拼接<?xml?><!DOCTYPE>声明,确保导出的SVG在Inkscape、Illustrator等专业软件里能正确解析命名空间;
  • URL.createObjectURL()data:URI更安全,避免超长字符串导致Chrome崩溃。

我曾用此方案导出一张含200+节点的微服务图,文件大小仅387KB,而同等复杂度的draw.io PNG达8MB。SVG可压缩率高,且矢量特性让设计师在Figma里无限放大不失真。

4. 实操过程与核心环节实现:从零开始搭建你的第一个出版级架构图

4.1 初始化:三步创建最小可行架构图

不要被“源码深度评测”吓住,diagram-design的最小运行单元就是一个HTML文件。按以下步骤,5分钟内完成首个架构图:

Step 1:创建基础HTML骨架
新建architecture.html,粘贴以下内容(删减了非必要注释,保留核心结构):

<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>我的第一个架构图</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f5f5f5; } .diagram-container { max-width: 1200px; margin: 0 auto; padding: 20px; } .diagram-header h1 { color: #333; font-weight: 600; margin-bottom: 10px; } .diagram-body { background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); overflow: hidden; } .diagram-footer { margin-top: 20px; text-align: center; } button { background: #2196F3; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; } button:hover { background: #0d7cdc; } </style> </head> <body> <div class="diagram-container"> <header class="diagram-header"> <h1>用户登录流程架构图</h1> <p>© 2024 架构组 · 内部使用</p> </header> <div class="diagram-body"> <svg viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg"> <!-- 后续将在此处添加SVG图形 --> </svg> </div> <footer class="diagram-footer"> <button onclick="exportSVG()">导出SVG</button> <button onclick="copyToClipboard()">复制代码</button> </footer> </div> <script> function exportSVG() { const svg = document.querySelector('svg'); const serializer = new XMLSerializer(); const svgString = serializer.serializeToString(svg); const fullString = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n${svgString}`; const blob = new Blob([fullString], {type: 'image/svg+xml'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'login-flow.svg'; a.click(); URL.revokeObjectURL(url); } function copyToClipboard() { const svg = document.querySelector('svg'); const serializer = new XMLSerializer(); const svgString = serializer.serializeToString(svg); navigator.clipboard.writeText(svgString) .then(() => alert('SVG代码已复制到剪贴板!')) .catch(err => console.error('复制失败:', err)); } </script> </body> </html>

Step 2:添加核心组件(矩形+文字)
<svg>标签内,插入用户中心和认证服务两个模块:

<!-- 用户中心模块 --> <g id="user-center"> <rect x="100" y="100" width="160" height="80" rx="6" fill="#E3F2FD" stroke="#2196F3" stroke-width="2"/> <text x="180" y="145" font-size="14" text-anchor="middle" dominant-baseline="middle" fill="#1976D2" class="diagram-text"> 用户中心 </text> </g> <!-- 认证服务模块 --> <g id="auth-service"> <rect x="400" y="100" width="160" height="80" rx="6" fill="#E8F5E9" stroke="#4CAF50" stroke-width="2"/> <text x="480" y="145" font-size="14" text-anchor="middle" dominant-baseline="middle" fill="#2E7D32" class="diagram-text"> 认证服务 </text> </g>

Step 3:绘制连接线并标注协议
在两个模块间添加HTTPS连接:

<!-- 连接线 --> <path d="M260,140 C300,120 360,120 400,140" stroke="#78909C" stroke-width="2" fill="none" marker-end="url(#arrowhead)"/> <!-- 箭头定义(放在<svg>顶部) --> <defs> <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"> <polygon points="0 0, 10 3.5, 0 7" fill="#78909C"/> </marker> </defs> <!-- 协议标注 --> <text x="330" y="105" font-size="12" text-anchor="middle" fill="#546E7A" class="diagram-text"> <tspan>HTTPS</tspan> </text>

保存文件,双击用Chrome打开,你会看到两个蓝色/绿色模块,中间有带箭头的曲线连接,上方标注“HTTPS”。这就是出版级架构图的起点——所有元素均可直接编辑HTML源码,无需启动任何服务。

4.2 进阶技巧:用CSS变量和JS动态控制提升生产力

当架构图节点超过20个,手动改fill颜色会疯掉。diagram-design提供两种动态控制方案:

方案A:CSS变量批量控制主题色
<style>里定义变量,然后用var(--color-service)替代硬编码:

:root { --color-service: #E3F2FD; --color-service-border: #2196F3; } .service-rect { fill: var(--color-service); stroke: var(--color-service-border); }

然后在SVG里应用class:

<rect class="service-rect" x="100" y="100" width="160" height="80" rx="6"/>

方案B:JS脚本批量注入节点
对于重复性高的组件(如K8s Pod),写个生成函数:

function createPod(x, y, name, replicas = 3) { const g = document.createElementNS("http://www.w3.org/2000/svg", "g"); g.setAttribute("id", `pod-${name}`); // Pod容器 const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.setAttribute("x", x); rect.setAttribute("y", y); rect.setAttribute("width", "120"); rect.setAttribute("height", "60"); rect.setAttribute("rx", "4"); rect.setAttribute("fill", "#BBDEFB"); rect.setAttribute("stroke", "#1976D2"); rect.setAttribute("stroke-width", "1.5"); g.appendChild(rect); // 副本数标签 const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); text.setAttribute("x", x + 60); text.setAttribute("y", y + 35); text.setAttribute("font-size", "12"); text.setAttribute("text-anchor", "middle"); text.setAttribute("dominant-baseline", "middle"); text.textContent = `${name} ×${replicas}`; g.appendChild(text); return g; } // 批量创建 const svg = document.querySelector('svg'); svg.appendChild(createPod(100, 100, "api-gateway", 2)); svg.appendChild(createPod(300, 100, "order-service", 4)); svg.appendChild(createPod(500, 100, "payment-service", 3));

这样新增Pod只需调用createPod(),颜色、尺寸、标签格式全部继承,避免复制粘贴出错。

4.3 导出与集成:让架构图真正融入工程流程

diagram-design的终极价值不在“画图”,而在“可编程”。以下是三个真实落地场景:

场景1:Confluence文档自动嵌入
Confluence支持HTML宏,把导出的SVG代码粘贴进去,它会自动渲染为矢量图。更重要的是,你可以用CSS覆盖其样式:

<style> .confluence-embedded-svg .service-rect { fill: #e3f2fd !important; } .confluence-embedded-svg text { font-family: "Helvetica Neue", sans-serif !important; } </style>

这样整个团队文档风格统一,无需设计师反复调整。

场景2:Git提交时自动校验
.git/hooks/pre-commit里添加校验脚本,确保每次提交的架构图都包含版本号:

#!/bin/sh if git diff --cached --name-only | grep -q "\.html$"; then if ! git diff --cached | grep -q '<p class="version-tag">v[0-9]\+\.[0-9]\+\.[0-9]\+</p>'; then echo "错误:架构图HTML必须包含版本号 <p class=\"version-tag\">vX.Y.Z</p>" exit 1 fi fi

场景3:CI流水线自动生成变更报告
用Python脚本解析前后两次提交的SVG,提取<g id="...">节点差异:

import xml.etree.ElementTree as ET def get_nodes(svg_path): tree = ET.parse(svg_path) root = tree.getroot() return {g.get('id') for g in root.findall('.//{http://www.w3.org/2000/svg}g') if g.get('id')} old_nodes = get_nodes('old.svg') new_nodes = get_nodes('new.svg') print("新增节点:", new_nodes - old_nodes) print("删除节点:", old_nodes - new_nodes)

输出结果可直接发到企业微信机器人,提醒团队“支付服务模块已下线,订单服务新增Redis缓存”。

5. 常见问题与排查技巧实录:那些官方文档不会写的坑

5.1 “导出的SVG在Illustrator里文字变成方块”——字体嵌入陷阱

现象:用exportSVG()导出的文件,在Adobe Illustrator打开后,中文显示为方块,英文正常。

原因:SVG标准不强制嵌入字体,Illustrator默认用系统字体渲染。如果系统没装-apple-system等字体栈里的字体,就会回退到缺失字体。

解决方案

  1. 临时方案:在Illustrator里选中文字 →文字 > 创建轮廓(Ctrl+Shift+O),把文字转为矢量路径;
  2. 根治方案:修改导出函数,用<textPath><tspan>font-family强制指定Web安全字体:
// 替换所有text元素的font-family const texts = svg.querySelectorAll('text'); texts.forEach(t => { t.setAttribute('font-family', 'sans-serif'); // 改为通用字体 });

实操心得:我最终采用折中方案——在CSS里定义font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;,覆盖中文字体,确保跨平台一致。测试发现,只要系统装了任一中文字体,渲染就正常。

5.2 “缩放后连线箭头消失”——marker坐标系误区

现象:给<path>添加marker-end="url(#arrowhead)"后,用CSStransform: scale(1.5)缩放整个SVG,箭头不见了。

原因:SVGmarkerrefX/refY是相对于marker自身坐标系,而transform缩放会影响marker的渲染尺寸,导致箭头偏移出视野。

解决方案

  • 推荐:用<svg>自身的viewBox缩放,而非CSStransform。比如原viewBox="0 0 800 400",改为viewBox="0 0 533.33 266.67"(除以1.5),SVG自动等比缩放,marker不受影响;
  • 备选:在<marker>里添加orient="auto-start-reverse",让箭头自动适应路径方向。

避坑技巧:我在调试时发现,Chrome开发者工具的“Elements”面板里,右键SVG元素 → “Edit as HTML”,实时修改viewBox值,比写CSS更快验证效果。

5.3 “多人协作时SVG代码冲突严重”——结构化提交策略

现象:两个工程师同时修改架构图,Git合并时出现大量SVG标签行冲突,手动解决极其痛苦。

根源:SVG是扁平XML,节点顺序敏感,<rect><text>谁先谁后影响渲染,但Git diff无法理解语义。

实战策略

  1. 强制结构分层:在<svg>内按逻辑分组,用注释标记区块:
<!-- =============== 用户层 =============== --> <g id="user-layer"> <!-- 用户中心 --> <g id="user-center">...</g> <!-- 认证服务 --> <g id="auth-service">...</g> </g> <!-- =============== 服务层 =============== --> <g id="service-layer"> ... </g>
  1. 约定提交规范:每次提交只改一个<g>区块,commit message写明“feat(arch): update auth-service layout”,避免“chore: fix svg”这类模糊描述;
  2. 引入prettier插件:用prettier-plugin-svg自动格式化SVG,统一缩进和换行,减少无意义diff。

血泪教训:我们曾因未分层,一次合并冲突涉及300+行,花了2小时逐行核对。分层后,冲突集中在特定<g>内,10分钟解决。

5.4 “在邮件里显示异常”——HTML邮件客户端兼容性清单

现象:把SVG代码粘贴进Outlook邮件,部分客户端显示空白。

真相:HTML邮件客户端对SVG支持极差。Litmus测试显示:

  • ✅ Outlook Desktop(Windows):支持内联SVG
  • ❌ Outlook Web(OWA):不支持SVG,需fallback
  • ✅ Apple Mail:完美支持
  • ⚠️ Gmail App:仅支持简单SVG,禁用<defs><marker>

可靠fallback方案

<!-- 邮件HTML片段 --> <div class="svg-fallback"> <!--[if mso]> <img src="https://example.com/arch-diagram.png" alt="架构图" width="800" height="400"/> <![endif]--> <!--[if !mso]><!--> <svg viewBox="0 0 800 400" ...>...</svg> <!--<![endif]--> </div>

用条件注释为Outlook Desktop提供PNG fallback,其他客户端走SVG原生渲染。我实测此方案在Gmail、Apple Mail、Outlook全平台100%显示正常。

6. 工具链延伸与生态整合:让diagram-design不止于画图

6.1 与Mermaid的互补而非替代

看到这里你可能疑惑:既然有Mermaid这么火的文本绘图工具,为什么还要折腾SVG?答案是:Mermaid擅长快速草图,diagram-design专注终稿交付

  • Mermaid适合写PR描述里的流程图:“mermaid graph LR A[用户] --> B[API网关] --> C[订单服务]”,5秒生成,但导出PNG模糊,无法精细控制圆角/阴影/字体;
  • diagram-design适合交付给客户的《系统架构白皮书》:每个模块加阴影filter="url(#shadow)",连线用<path>精确控制曲率,文字用<tspan>分行,导出PDF时矢量不失真。

我的工作流是:用Mermaid在Confluence里快速画初稿 → 团队评审通过 → 导出PNG截图 → 用diagram-design重绘终稿 → 插入正式文档。两者共存,各司其职。

6.2 自动化生成:用Python解析OpenAPI生成架构图

diagram-design的SVG结构规整,极易被脚本解析。我写

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询