上传文件
  1. base64hub
  2. 学习 Base64
  3. Data URI 性能
性能

Data URI 性能:什么时候内联 Base64 才划算

把文件内联成 Base64 data URI 能省掉一次请求,代价是更多字节、失去独立缓存,有时还会拖慢渲染。压缩会改变这笔账,而对 SVG 的影响又不一样。

data URI 的代价

data URI 把文件放进 URL 中:data:image/png;base64,iVBORw0KGgo...。凡是能用 URL 的地方都能用它,所以图标可以直接写在 HTML 的 src 属性或 CSS 的 background-image 中,浏览器不必为它单独发请求。

需要权衡的有:

  • 体积。Base64 会让载荷变成原来的 4/3,大约增大 33%(精确公式见 Base64 体积开销)。data:image/png;base64, 前缀还要再加 22 个字节。
  • 缓存。内联的文件只能作为所在文档的一部分被缓存。内联在每个 HTML 页面中的 logo 会随每个页面重新下载。内联在 CSS 文件中时,它和 CSS 一起缓存,改动一个图标就会让整个样式表的缓存失效。
  • 渲染。在阻塞渲染的 CSS 到达之前,浏览器什么都画不出来。如果样式表里内联了 200 KB 的字体或图片,首次渲染就会推迟这些字节下载所需的时间。在 HTML 中,内联数据会把它后面的内容在字节流中往后推。
  • 无法懒加载。给使用 data URI 的 <img> 加 loading="lazy" 毫无作用,因为字节已经随页面一起到达了。

相应的好处是省掉了一次请求。在 HTTP/1.1 下这很重要,因为浏览器对每个主机只开很少几个连接。到了 HTTP/2 和 HTTP/3,许多请求共用一个连接,节省就小得多了:主要是请求头和响应头,以及浏览器较晚发现的文件所需的一次往返延迟。

实测:gzip 和 Brotli 对 Base64 的影响

33% 这个数字针对的是未压缩的文本。大多数 HTML 和 CSS 都以 gzip 或 Brotli 传输,所以真正重要的是压缩后的大小。我们自己测了一下。

方法。样本文件取自 Debian 13 的软件包:一个 16×16 的 PNG 图标(HighContrast 图标主题)、一个 48×48 的 RGBA PNG(hicolor 主题)、2880×1800 的 Xfce 蓝色壁纸 JPEG、用 Pillow 以质量 80 从中缩小得到的 800×500 JPEG、Roboto Slab Regular WOFF2 字体,以及两个 SVG 图标(一个 479 字节的 Adwaita symbolic 图标和 7.6 KB 的 GNOME Debian 漩涡标志)。每个文件及其 Base64 编码(只有载荷,不含 data: 前缀)都用 Node.js 24 的 zlib 压缩:gzip 级别 9、Brotli 质量 11,这是预压缩静态资源的常用设置。单位为字节。

文件原始Base64原始 gzipBase64 gzip原始 BrotliBase64 Brotli
PNG 16×16281376285306285284
PNG 48×484,5526,0724,5754,6024,5514,589
JPEG 800×5008,19510,9287,4257,1737,1606,908
JPEG 2880×1800197,011262,684182,684183,945180,114181,140
WOFF2 字体52,53270,04452,53852,76552,53652,690
SVG 图标479640291427247410
SVG 漩涡7,61010,1483,1234,3982,5823,823

这些数字说明:

已压缩的二进制文件几乎完全抵消了开销。PNG、JPEG 和 WOFF2 的数据本来就是压缩过的,接近随机,再压缩一次收益很小(两个 PNG 在 gzip 后甚至变大了一点)。随机字节的 Base64 使用 64 个符号,每个占 8 比特,而 Huffman 编码会把它压回到大约每个符号 6 比特。压缩后的 Base64 是压缩后二进制文件的 96% 到 107%,是原始文件的 84% 到 109%。这里的 JPEG 本身还能再压缩一点,所以它们的 Base64 版本甚至比 gzip 后的 JPEG 还小一些;这点差异太小,不值得作为依据。极小的 16×16 PNG 上,gzip 固定的 18 字节头部和尾部占比显得很大,而 Brotli 更轻量的封装把这部分消除了。

SVG 反而更糟。SVG 是充满重复的文本,非常适合压缩:漩涡标志用 gzip 从 7,610 字节压到了 3,123 字节。Base64 把每 3 个字节打散成 4 个字符,字符的值取决于对齐位置,所以重复的子串不再呈现相同的样子。gzip 后的 Base64 SVG 比 gzip 后的 SVG 大 41–47%,用 Brotli 则大 48–66%。

URL 编码的 SVG 与 Base64

SVG 本来就是文本,根本不需要 Base64。用 URL 编码的 data URI 可以保持可读:

.flag {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 0v16h2V8h3l1 2h5V2h-4L9 0z'/%3E%3C/svg%3E");
}

至少需要转义 %、#(它会开启 URL 片段)、< 和 >,以及用来包裹属性的那种引号。把 SVG 内部的双引号换成单引号,就不必转义它们了。非 ASCII 字符必须按 UTF-8 做百分号编码。

同样两个 SVG,用同样的方法测量:

SVG编码方式大小GzipBrotli
图标Base64640427410
图标最小 URL 编码493293253
图标encodeURIComponent723326279
漩涡Base6410,1484,3983,823
漩涡最小 URL 编码7,1793,0572,549
漩涡encodeURIComponent11,5943,3192,750

“最小”是指合并空白、把双引号换成单引号,只转义 %、#、<、>。它压缩后的大小与原始 SVG 相当;漩涡标志甚至还略小一些,因为合并空白去掉了一些字节。完整的 encodeURIComponent 未压缩时更大,但压缩效果仍远好于 Base64。SVG 转 data URI 工具会把两种形式并排生成出来。

当某个工具或 API 只接受 Base64,或者 SVG 中有你不想费心转义的字符时,SVG 用 Base64 仍然是合理的。

内容安全策略与 data:

在内容安全策略(CSP)下,'self' 不包含 data: URL。每个需要允许它的指令都必须显式列出这个协议:

Content-Security-Policy: default-src 'self'; img-src 'self' data:; font-src 'self' data:

在 img-src 和 font-src 中允许 data: 很常见,风险也低。在 script-src、default-src 或 object-src 中允许它就是另一回事了:能注入标记的攻击者无需任何外部主机就能提供脚本,CSP 的意义也就大打折扣。如果内联图片触发了你的策略,只放宽 img-src 即可。

浏览器还会阻止页面把顶层窗口导航到 data: URL,无论是通过链接、重定向还是脚本。所以 data URI 可以嵌入页面,但不能作为顶层链接打开。

什么时候值得内联

通常值得:

  • 极小的图标和界面图片,大约 1–2 KB 以下,尤其是首次绘制就需要的。请求开销在它们的体积中占比很大,而压缩会抹掉大部分 Base64 的代价。
  • 单文件 HTML。离线报告、导出的文档、测试覆盖率页面,以及必须能从本地磁盘直接打开的 HTML 邮件预览。
  • 关键 CSS,它本来就内联在 HTML 中,小背景图可以跟着一起走。
  • 生成的内容,例如脚本把图表渲染成 PNG 后嵌入 HTML 报告。

通常不值得:

  • 照片以及任何超过几 KB 的内容。它们更适合作为独立文件,设置较长的缓存时间。
  • 在很多页面中共用的图片,内联后每个 HTML 响应都要重新下载一遍。
  • Web 字体。一个 WOFF2 文件通常有几十 KB(上面测量的那个是 52 KB),内联到 CSS 中会让样式表阻塞渲染的时间更长,而独立的字体文件可以被缓存和预加载。
  • 首屏以下的任何内容。带 loading="lazy" 的独立文件在真正需要之前不会产生任何开销。

电子邮件要单独考虑。各邮件客户端对 data: 图片的支持程度不一,请在收件人实际使用的客户端中测试。在 HTML 邮件中嵌入图片的可靠方式是用 cid: 引用的内联 MIME 附件,它同样是 Base64,只不过位于 MIME 结构之中。见 电子邮件中的 Base64。

把图片转成 data URI

拖入 PNG、JPEG、WebP 或 SVG,即可得到 data URI 及其大小。文件始终留在你的浏览器中。

图片转 Base64

常见问题

Base64 图片比普通图片加载更慢吗?

本身不会。小的内联图片可以省掉一次请求;大的则会让 HTML 或 CSS 变臃肿,无法单独缓存,还可能推迟渲染。几 KB 以下内联有好处,超过这个大小就得不偿失。

gzip 能消除 Base64 的 33% 开销吗?

对 PNG、JPEG 和 WOFF2 几乎能完全消除:在我们的测量中,gzip 后的 Base64 与二进制文件相差不过几个百分点。对 SVG 和其他文本则不行,gzip 后的 Base64 SVG 比 gzip 后的 SVG 大 41–47%。

内联 SVG 用 URL 编码好还是 Base64 好?

用 URL 编码。它可读、可编辑,压缩效果和原始 SVG 差不多。至少要转义 #、%、< 和 >;如果属性用双引号包裹,SVG 内部就改用单引号。

为什么我的 data URI 图片被内容安全策略(CSP)拦截了?

CSP 把 data: 当作独立的协议,'self' 不包含它。请在 img-src(字体则是 font-src)中加上 data:,不要把它加到 script-src 或 default-src 中。

继续阅读