跳到主要内容

SVG 转 Data URI:把图标内联进 CSS 背景与 img,省掉一次请求

讲清楚怎么把 SVG 内联成 data URI,粘进 CSS background 或 img src,省掉额外 HTTP 请求,对比 URL 编码和 base64 谁更小,以及特殊字符怎么转义不踩坑。

发布于 作者 李雷
#svg #data-uri #css #前端性能

SVG 转 Data URI:把图标内联进 CSS 背景与 img,省掉一次请求

页面上一个搜索图标、一个箭头、一条分割线,各自一个 .svg 文件,每个都要单独走一趟 HTTP。图标本身可能才几百字节,可一次请求的往返加上 DNS、TLS、排队,常常比文件本身还贵。把这些小矢量直接内联进 CSS 或 HTML,就能用几行编码后的字节,换掉一次完整的网络往返。这篇讲清楚怎么转、用哪种编码、以及哪些字符不转就会出问题。

什么是 SVG 的 Data URI

Data URI 是一种把文件内容直接写进 URL 的格式,浏览器看到 data: 开头就知道后面不是地址,而是内容本身。SVG 的 data URI 长这样:data:image/svg+xml,<编码后的源码>。它能放在任何接受 URL 的地方,最常见的两处是 CSS 的 background-image: url(...) 和 HTML 的 <img src="...">。一旦内联,这个图标就跟着 CSS 或 HTML 一起到达浏览器,首屏直接渲染,不会有图片还没加载完的那一下空白闪烁。

代价也很直接:内联的字节烤进了承载它的文件,没法和那个文件分开缓存。所以内联只适合小资源,图标、logo、简单装饰图形,编码后大概 4 到 8 KB 以内最划算。一张几十 KB 的复杂插画,内联进去会随每个引用它的页面重新下载,这种就老老实实外链 .svg

URL 编码比 base64 小,而且小很多

这是最该记住的一点:SVG 用 URL 编码,几乎总是比 base64 小,典型图标能小 20% 到 40%。

原因在于两种编码的工作方式完全不同。base64 是机械的,它把每 3 字节输入变成 4 个 ASCII 字符,固定多出约 33%,跟你的内容是什么毫无关系。而 SVG 本身几乎全是 ASCII:标签名、属性名、路径里的字母数字,本来就是可以直接放进 URL 的安全字符。URL 编码把这些原样留着,只转义少数在 url(...) 里会出问题的字符。

一个典型图标里,需要转义的字符可能只占 5% 到 10%,而 base64 是把 100% 的字节都撑大。所以差距不是一点点。如果你接手一个样式表,发现前人把每个内联 SVG 都 base64 了,把它们换成 URL 编码形式,CSS 包体积往往能实打实掉一截,而且页面看起来一模一样。

哪些字符必须转义,不转就出 bug

URL 编码不是全转,只转那几个会破坏 url(...) 解析的字符。最容易踩的是井号 #

在 data URI 里,# 是 URL 片段标识符的开始,浏览器会把它后面的一切当成页面锚点,而不是 SVG 的内容。这意味着一个没转义的 fill="#ff0000" 会在井号那里把整个图像悄悄截断,结果就是渲染成一片空白,而且控制台连个错都不报。所以 # 必须变成 %23

同样要处理的还有:% 本身是转义字符,字面的百分号得变成 %25;界定 XML 标签的尖括号 < > 也要转;放进 url("...") 双引号里时,源码里的双引号也得转,免得提前把字符串截断。我自己手写过几次 data URI,十有八九栽在井号上,图标渲染不出来排查半天,最后发现是某个 fill="#..." 的井号没转。后来就不再手搓了,直接用工具把这一组字符一次转干净,既正确又只转该转的,输出还保持紧凑。

一个真实例子:把图标内联进 CSS 背景

假设有这么一个简单的对勾图标:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path fill="#16a34a" d="M6 12 2 8l1.5-1.5L6 9l6.5-6.5L14 4z"/>
</svg>

URL 编码后内联进 CSS,得到的规则大致是这样:

.check::before {
  content: "";
  display: inline-block;
  width: 16px;
  height: 16px;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Cpath%20fill='%2316a34a'%20d='M6%2012%202%208l1.5-1.5L6%209l6.5-6.5L14%204z'/%3E%3C/svg%3E");
}

注意三处:尖括号变成了 %3C %3E,空格变成 %20,而 fill 里的井号变成了 %23,绿色 #16a34a 写成了 %2316a34a。其余字母、数字、路径数据原样保留,所以编码后还能肉眼读个大概。把这条规则直接粘进样式表就能用,网络面板里这个图标的请求数是零。如果你想确认 base64 在你这个文件上到底大多少,工具里的并排体积对比会给出确切字节数。

内联之前,先优化和确认编码

两个收尾习惯能让内联效果再好一点。

第一,内联前先优化。SVG 导出时常带一堆注释、多余空白、冗余属性,这些字节编码后照样占位。先用 svg-optimizer 把源码压一遍,去掉编辑器塞进去的元数据,再来编码,内联体积能再小一截。

第二,选对编码并验证。绝大多数场景 URL 编码就是更优解,但有些消费方明确处理不了 URL 编码的负载,这时才用 base64。如果你的目标根本不是 SVG,比如要把一张 PNG 内联进邮件,那就换 base64-image-converter,位图没有 URL 编码的便宜可占,base64 是正路。把 SVG 源码粘进 SVG 转 Data URI 工具,它会同时给出 URL 编码和 base64 两种结果、并排体积对比,以及可直接粘的 CSS、HTML、纯 URI 三种输出,该转义的字符全自动处理,不用你记那几个百分号。

还有一个常被忘掉的细节:经 <img> 或 CSS url() 加载的 SVG 是当独立文档解析的,必须自带 xmlns="http://www.w3.org/2000/svg",否则不渲染。这和写在 HTML 里的内联 <svg> 不一样,后者可以省 xmlns。从设计工具导出的 SVG 一般都带,但手写或裁剪过的要留意补上。

把这几步走顺了,小图标内联就成了纯收益的事:少一次请求,首屏没闪烁,字节还比 base64 少。


Made by Toolora · Updated 2026-06-13