SVG 排错清单¶
按症状查表。找到你的现象,照着「排查动作」一步步做。
通用三板斧(任何问题先做这三件)¶
1. 先验证 XML 是否合法¶
SVG 本质是 XML,语法要求比 HTML 严格得多——标签没闭合、属性值没加引号,整张图直接白屏,且没有任何报错提示。
一行命令验(需要 Python):
- 输出
XML OK→ 语法没问题,继续查别的原因。 - 报错并给出行号 → 直接定位到那一行修。
2. 用浏览器直接打开 .svg 文件¶
把文件拖进 Chrome / Edge(或用 VS Code 的 SVG 预览插件)。这一步把"站点/构建"这一层因素排除掉:
- 浏览器直接打开就是坏的 → 问题在 SVG 文件本身。
- 浏览器打开是好的、放进站点后坏了 → 问题在引用方式或路径,见 亮暗双主题与 MkDocs 嵌入。
3. 二分法定位(对付"几百行改了一处就坏了")¶
- 备份一份文件。
- 注释掉文件后半部分(用
<!--...-->包住),看前半部分是否正常。 - 正常 → 问题在后半段;不正常 → 问题在前半段。
- 对有问题的那半边重复此操作,几轮就能缩到十几行以内。
注释不能嵌套
<!-- <!-- 内层 --> --> 是非法的。如果原文件里已有注释,二分时不要用包裹式注释,改成直接删除目标段落(记得先备份)。
症状对照表¶
图完全不显示 / 白屏 / 只显示一个破图标¶
| 可能原因 | 怎么确认 | 解法 |
|---|---|---|
| XML 语法错误 | 三板斧第 1 步报错 | 按报错行号修。最常见:标签未闭合、属性值缺引号、& 未转义 |
| 文件编码问题 | 文件含 BOM 或被存成了 UTF-16 | 用编辑器另存为 UTF-8(无 BOM) |
| 路径 / 404 | 浏览器开发者工具 Network 面板看该图请求状态 | 见 亮暗双主题与 MkDocs 嵌入 §路径与文件名编码 |
<svg> 缺命名空间 | 检查根标签有无 xmlns="http://www.w3.org/2000/svg" | 补上。内联在 HTML5 里可省略,独立 .svg 文件必须有 |
特殊字符转义是最隐蔽的一种:
需要转义的字符:& → &、< → <、> → >。
图只显示了一部分 / 边缘被裁掉¶
| 可能原因 | 怎么确认 | 解法 |
|---|---|---|
| 内容超出 viewBox | 找出所有元素的最大 x/y,与 viewBox 的宽高对比 | 加大 viewBox 后两个数字(viewBox="0 0 920 720" → 0 0 920 800) |
被 clipPath 裁剪 | 搜文件里有没有 clip-path | 调整裁剪区域,或去掉 clip-path 属性 |
| 外层容器 CSS 溢出隐藏 | 浏览器里检查父元素的 overflow | 给容器加 overflow: visible,或缩小 SVG |
快速判断是否超出 viewBox
临时在文件末尾(最后一个子元素之后、</svg> 之前)加一个覆盖全画布的红框:
改了但没效果¶
| 可能原因 | 怎么确认 | 解法 |
|---|---|---|
| 改错元素 | 用 读图第 ④ 步 的亮色试改验证 | 重新定位 |
| 被后面的元素覆盖 | SVG 后绘制的在上层——后面的元素会盖住前面的 | 把目标元素往后移(移到同层最后),或给它加 z-index(仅内联时有效) |
| 浏览器缓存了旧图 | 强制刷新(Ctrl+F5);或给 URL 加个无关参数 | demo.svg?v=2 |
| 元素被隐藏 | 检查有没有 display="none" / visibility="hidden" / opacity="0" | 去掉隐藏属性 |
改的是 <defs> 里的定义,但引用处覆盖了样式 | 检查 <use> 元素上是否单独写了 fill/stroke | 引用处的样式优先级更高,改引用处 |
SVG 没有 z-index,靠绘制顺序决定层叠
内联 SVG 时才可以用 CSS 的 z-index。作为独立 .svg 文件时,唯一的层叠规则就是:写在后面的元素盖在上面。
元素跑到了意料之外的位置¶
| 可能原因 | 怎么确认 | 解法 |
|---|---|---|
嵌套 <g transform> 累加 | 从目标元素往上一级级找父 <g>,把所有 transform 加起来 | 见 改图的六种常见手术 手术四 |
| 旋转中心不对 | 检查 rotate(角度, cx, cy) 里的 cx, cy | 省略中心时会绕原点 (0,0) 转,通常不是你想要的。补上旋转中心 |
<use> 的 x/y 是平移 | <use href="#n" x="50" y="30"> 等价于整体平移 (50,30) | 定义时把图元画在原点附近,引用时定位最直观 |
| y 轴方向搞反 | 想上移却加大了 y | y 轴朝下:上移 = y 减小 |
旋转是新手最容易翻车的一项:
<!-- ❌ 绕原点旋转,元素会"飞走" -->
<rect x="100" y="100" width="40" height="20" transform="rotate(45)"/>
<!-- ✅ 绕自身中心旋转 -->
<rect x="100" y="100" width="40" height="20"
transform="rotate(45, 120, 110)"/> <!-- 中心 = (100+20, 100+10) -->
颜色不对 / 填充成黑色 / 描边看不见¶
| 现象 | 原因 | 解法 |
|---|---|---|
| 填充莫名是黑色 | 元素没写 fill,继承默认值(黑) | 显式写 fill="none"(不想填充时)或指定颜色 |
| 描边看不见 | 只写了 stroke 但没写 stroke-width(默认可能为 1 或继承自外部) | 补 stroke-width="2" |
path 被填充了一块奇怪的实心区域 | path 的 d 没有闭合,SVG 会自动连回起点再填充 | 加 fill="none",或在 d 末尾加 Z 显式闭合 |
| 暗色主题下看不清 | 颜色写死了 | 见 亮暗双主题与 MkDocs 嵌入 |
| 内联后样式异常 | 被站点全局 CSS 影响 | 用更具体的选择器,或给元素加内联 style(优先级最高) |
文字相关¶
| 现象 | 原因 | 解法 |
|---|---|---|
| 中文显示为方块 □□□ | 渲染环境缺中文字体 | 指定字体族(见下方);或把文字转成路径(转曲,失去可编辑性) |
| 文字位置偏高/偏低 | y 是基线不是顶部 | 加 dominant-baseline="middle" 并把 y 设为目标中心 |
| 文字重叠 | 间距不够 | 调 y/dy、缩小 font-size、改 text-anchor(见手术五) |
| 文字被裁掉 | 超出 viewBox 或被 clipPath 裁切 | 加大 viewBox;检查裁剪区域 |
指定中文字体族:
多个字体用逗号分隔,渲染时从前到后依次尝试,第一个可用的生效。末尾一定要留一个通用族(
sans-serif)兜底。
箭头 / 渐变 / 滤镜不见了¶
| 现象 | 原因 | 解法 |
|---|---|---|
| 箭头消失 | <defs> 里的 marker 被删了,或 marker-end="url(#id)" 的 id 拼错 | 搜索 url(# 核对 id 与 <defs> 中的 id 是否完全一致(含大小写) |
| 渐变变成纯色/黑块 | fill="url(#grad)" 引用的 id 不存在 | 同上,核对 id |
| 滤镜效果没了 | filter="url(#blur)" 引用丢失 | 同上 |
| 删了某个元素后,多处同时出问题 | 删掉的是 <defs> 里被共享引用的定义 | 删 <defs> 内容前务必先搜 id 引用 |
id 是全局唯一的
SVG 里 id 在整个文档内必须唯一。如果内联了多张 SVG 到同一个页面,而它们用了相同的 id(比如都叫 arrow),就会产生冲突——浏览器只认第一个,后面的引用会指向错误的对象。 做法:内联多张 SVG 时,给每张图的 id 加前缀,例如 fig1-arrow、fig2-arrow。
图很模糊 / 放大后有锯齿¶
| 原因 | 怎么确认 | 解法 |
|---|---|---|
其实是位图(PNG/JPG 套了个 .svg 后缀,或 SVG 里嵌了 <image> 位图) | 放大看是否出现像素块;搜文件里有没有 <image | 真正的矢量图无论放多大都是平滑的。是位图就只能换素材 |
| 显示尺寸远小于 viewBox 尺寸 | 检查 width/height 与 viewBox 的比例 | 按需要调整显示尺寸 |
| 启用了非整数缩放 | 显示宽高与 viewBox 比例不匹配 | 一般影响不大,可忽略 |
放进 MkDocs 后的问题¶
| 现象 | 原因 | 解法 |
|---|---|---|
| 图片 404 / 不显示 | 相对路径基准错;或手动做了 URL 编码 | 见 亮暗双主题与 MkDocs 嵌入 §路径与文件名编码 |
裸 <img src> 在子页面 404(首页却正常) | 用裸 HTML <img src="assets/x.svg">;MkDocs 只重写 markdown  的链接,不重写裸 HTML 的 src。use_directory_urls 默认开启时每篇页变自身目录,子页层级下 assets/ 解析到错误目录 | 改用 markdown 图片语法 {: style="max-width:680px;width:100%;height:auto"},让 MkDocs 按页面深度自动重写相对路径(子页→../assets/...、首页→assets/...),与页面层级彻底解耦 |
| 构建时告警 "contains a link ... but ... not found" | 路径写错 | 按告警里的路径去核对 |
| 本地预览正常,部署后 404 | 文件名大小写不一致(Windows 不敏感,Linux 服务器敏感) | 统一大小写,推荐全小写 |
| 图太宽撑破页面 | .svg 里写死了 width | 删掉 width/height,只留 viewBox;或用 <img style="max-width:100%"> |
| 链接指向了完全无关的页面 | 见下方说明 | 链接到索引页时用站点绝对路径 |
在 MkDocs 里引用本地图片,优先用  而非裸 <img>
本站多次踩过这个坑:裸 HTML <img src="assets/x.svg"> 的 src 不会被 MkDocs 重写相对路径,而 use_directory_urls(默认 true)会让每篇页面变成自己的目录。结果——
- 页面在顶级目录(如
SVG/index.md→/SVG/)时,assets/碰巧解析正确,图显示正常; - 同一张图放在子页面(如
SVG/改图的六种常见手术.md→/SVG/改图的六种常见手术/)时,assets/被解析到不存在的/SVG/改图的六种常见手术/assets/,直接 404。
首页正常、子页挂掉,正是这个特征。正确写法:
MkDocs 会按每篇页面的实际深度自动重写路径(子页生成 ../assets/x.svg、首页生成 assets/x.svg),无论页面嵌套多深都不会错;GitHub Pages 子路径部署也安全,无需写死绝对路径。
样式用 attr_list 的 {: style="..."} 携带(Material 默认开启 attr_list 扩展),等价原先 <img> 上的 style 属性。
大小写是 Windows 用户的经典坑:本机预览一切正常(
Demo.svg和demo.svg都能打开),推到 Linux 服务器上就 404。文件命名统一小写可根治。
多图并排:最简单可靠的是「同一段落内多个 inline 图」,别用 flex 容器
要两张图并排,最朴素的写法是把两个 markdown 图片语法放在同一段落(中间用空格/换行分隔),各占约一半宽度——两个 <img> 是行内元素,天然左右排列,不依赖任何 HTML 容器(flex/grid 在 Typora/MkDocs 混合环境不可靠,曾导致 DW01 页两图垂直堆叠)。完整排版说明(含图注写法、为什么要 display:inline-block)见 嵌入页「多图并排与图注」:
{: style="display:inline-block;max-width:380px;width:47%;height:auto;vertical-align:middle" }
{: style="display:inline-block;max-width:380px;width:47%;height:auto;vertical-align:middle" }
- 两图必须在同一个 markdown 段落里(段落间空行会断开 → 变垂直);
width:47%给中间留出空隙。 display:inline-block防止主题把img强制成块级;vertical-align:middle让两图底部对齐。- 即使渲染器不支持
{: style}属性(如部分 Typora 场景),图也会按固有尺寸并排——只是可能偏小。 - 需要分图注时,在下方另起一段写合并说明,或用表格/图注段落,别用
<figure>+<figcaption>包裹 markdown 图(结构不稳)。 - 本站 实战 DW01 复盘页 曾试过
<figure markdown>+ flex 容器(产物 HTML 结构正确但实际垂直)与裸<img>(MkDocs 不重写路径 404)两套方案,最终回落到上面的 inline 写法。判断标准:构建后打开产物 HTML,两个<img>应位于同一个<p>内且src已被 MkDocs 按页面深度重写。
启用 wikilink 插件后,纯文件名链接会被全局匹配(很隐蔽)
许多 MkDocs 站点会装 wikilink 类插件(如 mkdocs-roamlinks-plugin),用来支持 [[双括号]] 写法。这类插件通常会把不带目录前缀的纯文件名链接也纳入"按文件名全局匹配"的逻辑。
后果:](index.md) 这种写法,本意是"当前目录下的 index.md",但插件会在整个 docs 目录里找所有叫 index.md 的文件——站点里往往有十几个——然后匹配到其中一个,最终链接指向一个毫不相关的页面。构建不报错、页面渲染正常,只是链接是错的,非常隐蔽。
判断方法:打开构建产物的 HTML,检查该链接的 href 是否指向了预期之外的目录。
规避办法:
<!-- 可能被误匹配(index.md 是重名文件) -->
[← 返回索引](./index.md)
<!-- 站点绝对路径(推荐,最稳) -->
[← 返回索引](/分类/子分类/)
<!-- 带目录前缀(同样能绕开纯文件名匹配) -->
[← 返回索引](../子分类/index.md)
插件连代码块里的示例也会一起改
上面第一行示例在构建时确实会被插件改写(本站在写这一页时就遇到过)。这是纯文本替换,与是否在代码块中无关——所以不要照抄页面源码里的示例文本,按"带前缀或绝对路径"的原则自己写即可。
文件名在站内唯一时(如 改图的六种常见手术.md)不受影响,出问题的基本都是 index.md 这类通用名。
速查卡¶
白屏 → 先验 XML 合法性(一行 python 命令)
只显示一半 → 查 viewBox 是否够大 / 有无 clipPath
改了没变化 → 亮色试改验证找对了没;检查是否被后绘制的元素盖住
位置跑偏 → 父级 <g> 的 transform 会累加;rotate 记得给旋转中心
填充黑块 → 补 fill="none"
中文方块 → 指定中文字体族
箭头消失 → 核对 url(#id) 与 <defs> 里的 id 是否一致(含大小写)
部署后 404 → 文件名大小写 / 相对路径基准
相关:← 亮暗双主题与嵌入 · 实战复盘 → · ← SVG 动手区