SVG 导出后必须删掉的五类垃圾:viewBox、currentColor 与体积优化

从 Figma、Sketch、Illustrator 导出的 SVG,一半体积和全部换色能力往往是被导出器自己弄丢的。删对五类内容即可恢复,并说清四种嵌入方式的边界。

约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证

导出文件的问题是可预测的

导出的 SVG 通常同时带着三个毛病:尺寸写死、颜色写死、体积虚高。三件事都不是设计稿的问题,而是导出器为了「在任何地方打开都长一样」而写入的冗余。清一次,一个图标从 8KB 掉到 1.2KB、并且第一次具备跟随 CSS 换色的能力,是很常见的结果。判断标准也很直接:如果这个文件不能只靠改 CSS 换色和改尺寸,它就还没清干净。还要分清两种压缩:删节点的清理是无损的,重算贝塞尔曲线、合并路径的简化是有损的,后者必须做像素比对,因为曲线降精度到肉眼可见只是时间问题。

viewBox 管坐标系,width/height 管初始尺寸

viewBox 定义内部坐标系,四个数字依次是 min-x、min-y、width、height;width 和 height 属性定义这套坐标系映射到页面上的初始尺寸。两者同时存在时,width/height 优先,viewBox 只负责缩放比例。所以要让 SVG 自适应,做法是删掉 width 和 height、保留 viewBox,再用 CSS 给尺寸:width:100%、height:auto,或者干脆 width:1em。注意删完之后默认宽度会变成 100%,在 flex 容器里可能直接撑满一行,所以 CSS 里显式给尺寸是必要的一步,不能只删不管。行内图标更推荐 width:1em、height:1em,让图标直接跟随 font-size,比写死 16px 更省事,也不会在字号放大时错位。还有一点:viewBox 的四个数字不必等于原始画布尺寸,只要比例一致,导出时被整体放大十倍也不影响渲染;让 viewBox 贴着路径的实际包围盒,缩放时留白最少。

同一个图标,清理前后

导出原样
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
     viewBox="0 0 24 24" fill="none" preserveAspectRatio="xMidYMid meet">
  <!-- Generator: Adobe Illustrator 27.0.0 -->
  <g id="Layer_1" data-name="Layer 1">
    <path id="path-1" d="M12 2.00000000L22 12.00000000L12 22.00000000Z" fill="#000000"/>
  </g>
</svg>
清理后:可换色、可缩放
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
  <path d="M12 2 22 12 12 22Z"/>
</svg>

四种嵌入方式的能力边界

嵌入方式能否换色能否单独缓存适用场景
内联 <svg>能,currentColor 和 CSS 选择器都生效否,随 HTML 一起传输需要换色、需要动画的图标
<img src="icon.svg">不能,外部文档拿不到页面 CSS能,独立缓存和 CDN内容型插画、与主题无关的图
CSS background-image不能,除非改用 mask-image能装饰背景、伪元素图标
<symbol> + <use>能,前提是 symbol 内用 currentColor能,外链 sprite 单独缓存成体系的图标库

内联的代价常被低估

内联确实换来换色能力,但代价不止「污染 DOM」四个字。图标一多,节点数成倍增长:一个两百行的列表页塞进三十个图标,节点数轻松翻倍,首屏解析和样式重算都变慢,而且这些 SVG 跟着 HTML 一起传输,无法被 CDN 单独缓存、也无法被 Service Worker 单独命中。判断方法很朴素:这个图标需要跟随主题换色、需要 hover 变色或动画吗,需要就内联;只是放在那里的插画就走 img,让浏览器缓存。同一套图标在一个页面里出现超过五六次,就该考虑 sprite 而不是逐个内联,因为 symbol 只解析一次,use 的渲染成本接近一个空元素。真正的分界线是「是否需要被 CSS 控制」,不是「是否更现代」。

必须删的五类

  1. 编辑器元数据与注释:Generator 注释、<metadata>、设计稿图层名,对渲染零贡献,却占掉多数可压缩体积。
  2. 空容器:没有子节点、或只剩 transform 的 <g>,逐层嵌套会把一条路径撑成四五层结构。
  3. 多余精度:路径坐标保留 3 位小数足够,1e-8 级别的尾数只是导出器的浮点残留,一个图标能省下几百字节。
  4. 无用 id 与 data-name:只有被 url()、<use> 或脚本引用时才需要 id,其余都是冲突源。
  5. 冗余默认值:preserveAspectRatio="xMidYMid meet" 就是默认值,写出来只是噪音。

id 是文档级的,不是 svg 级的

内联 SVG 里的 id 作用域是整个文档,不是它所在的那个 <svg>。同一个页面放两个图标,各自都带 <linearGradient id="a">,第二个渐变就会失效:fill="url(#a)" 解析到文档里第一个 #a,也就是第一个图标的渐变。症状是「单独打开正常、放进列表就变色」,非常难查。处理办法有三条:删掉不需要的 id;必须保留时加前缀(check-a、star-a);或者干脆不用渐变,换成纯色 currentColor。用 <use> 引 symbol 时同理,symbol 内部的 id 同样必须全局唯一。调试这类问题最快的办法是在 DevTools 的 Elements 面板里搜 url(#,逐个确认被引用的 id 在文档里只出现一次。

sprite 与无障碍写法

symbol sprite + role=img + title
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
  <symbol id="icon-check" viewBox="0 0 24 24">
    <path d="M20 6 9 17l-5-5" fill="none" stroke="currentColor"
          stroke-width="2" stroke-linecap="round"/>
  </symbol>
</svg>

<svg role="img" aria-labelledby="check-title" width="24" height="24">
  <title id="check-title">已通过校验</title>
  <use href="#icon-check"/>
</svg>

别把 fill-rule 当垃圾删掉

清理时最容易误删的是 fill-rule 和 clip-rule。它们决定多段路径怎么填充:evenodd 让内外环相减形成镂空,nonzero 让同向子路径合并。一个齿轮或圆环图标如果删掉 fill-rule="evenodd",中间的孔会被填满,图形从「环」变成「饼」,而文件确实变小了。clip-rule 影响 clipPath 的判定,原理相同。所以优化后必须肉眼比对一次,或者做像素级 diff;体积降了但图形变了,等于没优化。路径简化工具(比如把曲线降精度)也有同样的风险,先备份再压。还有一个更隐蔽的情况:某些优化器会把 fill 为 none、没有可见填充的路径判定为空元素直接删掉,描边图标于是整条消失,而文件大小看起来非常理想。

常见问题

为什么我的 SVG 换不了颜色?

路径上写了 fill="#000" 这类具体颜色。改成 fill="currentColor",颜色就会跟随 CSS 的 color。

删掉 width 和 height 后图标变得特别大?

SVG 默认宽度是 100%。删除宽高后必须在 CSS 里给 svg 或容器显式尺寸,例如 width:24px;height:24px。

内联 SVG 一定比 img 好吗?

不是。内联能被 CSS 着色,但要随 HTML 传输、无法单独缓存;不改色的插画用 img 更划算。

preserveAspectRatio 什么时候必须保留?

需要非等比拉伸或裁剪时,例如 none、slice。默认的 xMidYMid meet 可以直接删。

📂

松开以自动格式化

支持 .json / .sql / .csv / .html / .css / .xml / .yaml 文件