abox.tools

语言: 简体中文

什么时候该把图片塞进 CSS,什么时候不该

写进样式表里的图片,跟样式表一起到:不用再发一次请求,也不用等。代价是它不再是一个文件,没法单独缓存,而且它周围的东西一改,它就得重新下一遍。这一页讲这笔交易什么时候划算,什么时候悄悄地不划算。

打开图片转 Data URI 整张图变成一行文本,直接粘进 CSS 或者 HTML。

以 Markdown 查看

先说结论

打开图片转 Data URI,把图丢进去,选一个 CSS 自定义属性,把那一行粘到样式表开头。之后哪儿要用,就写 background-image: var(--logo)。

图小的时候这么干,图标、项目符号、小箭头、底纹这类,而且每个页面都要用。照片就别这么干。下面讲的,是这两句话为什么不一样,以及怎么判断你手上是哪一种。

data URI 到底是什么

一个把东西装在自己身体里、而不是指向它的地址。样式表本来会写

background-image: url("logo.png");

于是浏览器跑一趟去取 logo.png;而 data URI 写的是

background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...");

这时候没什么可取的,图已经在那儿了,以字符的形式写出来。它由三部分组成。data: 是协议。image/png 是媒体类型,而浏览器对它是完全采信的,这一点下面还会说。逗号后面那一整串,就是文件本身。

整个想法就这么点东西。它不是什么奇技淫巧,1998 年就进了标准,之后出的每个浏览器都支持。

它换来的:少一次往返

省下的不是流量,是那次请求。

浏览器得先读到提到 logo.png 的那份样式表,才能去要它;而它得先把样式表取回来,才能读。所以一张普通的背景图,在页面加载里至少埋着两次往返,而在慢网络上的手机里,一次往返可能就是两百毫秒上下,跟文件多小毫无关系。一个 600 字节的小箭头,传输几乎不花钱,到达却可能要花掉四分之一秒。

内联之后,它跟着样式表一起到。好处全部就在这儿,而对一个出现在首屏的小图标来说,这好处是实打实的。

它的代价:三分之一,外加缓存那笔账

base64 多出大约三分之一。三个字节的文件变成四个字符,因为用网址允许的那些字符去表示任意字节,代价就是这么多。没有哪个聪明的编码器能绕开它。一张 9 KB 的 PNG,进样式表就是 12 KB。

压缩还不回来。这一节正是大家想当然略过的。gzip 和 Brotli 靠的是找冗余,而 PNG、JPEG 和 WebP 早就压过一轮了,里头几乎没有冗余可找,base64 也不会添出冗余来。实际上你只能找回多出来那三分之一里的十分之一左右,绝不是全部。(SVG 正好相反,下一节专门说它。)

它不再是一个文件。这笔代价不会出现在你多半会做的任何一项测量里,而规模一大,要命的就是它:

几个粗略的分界线,它们是建议改变的地方,不是浏览器行为改变的地方:2 KB 以内是明摆着的赚;到 10 KB 上下,只要它每页都要用,一般还是值;过了 50 KB,那就是个错,而且没有任何报错提醒你。这个工具会告诉你每个结果落在哪一档,字符数就摆在旁边。

输出卡片:一条含 base64 数据 URI 的 CSS 规则,旁边是原文件大小和编码后的大小。
编码后的副本比来源文件大三分之一左右。这一节要讲的代价就是它,而且是写出来的,不是留给你自己去发现的。

绝不要给 SVG 上 base64

这是内联图片里最常见的一个错,而且导出插件和构建工具犯得不比人少。

SVG 是文本。网址本来就能装文本。只有少数几个字符必须转义,%、#、<、>,还有你用来包住它的那个引号,其余的原样留着就行。这么编出来的 URI,通常比同一个文件的 base64 短五分之一左右,而且之后是按文本压缩的,不是按噪声。

它还看得懂:

background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E...");

viewBox 你看得见。填充色也可以直接在编辑器里改,不用解码任何东西。同一个文件上了 base64,就变成一堵谁也不会再碰的字母墙。图片转 Data URI 对认出来是 SVG 的文件自动走这条路,另外给个人别工具链留了个勾选框,那种非 ;base64 不可的情况才用。

那个只坑 SVG 的引号问题

CSS 允许你写不带引号的 url(),对普通文件名来说这没问题:

background-image: url(logo.png);

同样的写法用在百分号编码的 SVG 上就完了。不带引号的 url() 记号,遇到第一个空格、括号、引号或者控制字符就结束,而 SVG 里到处是空格,每个属性之间、路径里每两个数字之间都有。于是这条声明变成无效声明,而 CSS 对无效声明是一声不吭地丢掉,你得到的是没有背景,也没有报错。

办法就是加引号,每次都加:

background-image: url("data:image/svg+xml,%3Csvg ... %3E");

这也正是编码器不需要转义空格的原因:引号包着的网址里,空格完全合法,而把每个空格都写成 %20,文件里有多少空格就得多掏多少个三倍字符。这两个决定是一起的:URI 加引号,空格就能原样留着。这个工具输出的每一种形式都加了引号,就是为了这个。

媒体类型必须写对

data URI 自己声明自己的类型,而浏览器完全采信这句话。这里没有取文件时那种嗅探兜底:一个其实是 JPEG 的东西,你声明成 image/png,图就是画不出来,而且哪儿都不会给你一句有用的提示。

这一点要紧,因为扩展名会撒谎。一张按 JPEG 导出、又被改名成 logo.png 的照片,在硬盘上是再平常不过的东西。而图片文件开头那几个字节说得毫不含糊,每种格式都有自己的签名,所以工具该读文件,不该读名字。这里的这个就是这么干的,两边对不上时还会告诉你一声。

有两种格式值得单说,因为它们失败得很让人摸不着头脑。HEIC,iPhone 拍照用的那个,还有 TIFF,扫描仪出的那个,都能生成完全合法的 data URI,可除了 Safari 没有浏览器画得出来。URI 没坏,只是网页本来就不支持这个格式。先转一道再说。

你没打算公开的那些元数据

data URI 是文件的逐字节副本。没有哪一步解码再重编,这通常正是重点,画质一点不掉,但同时也意味着文件里别的一切都跟着来了。

一张刚出手机的照片带着 EXIF:拍摄地的 GPS 坐标、时间戳、相机型号,常常还有它的序列号。这些能占到 30 KB。内联之后就是你样式表里 40 KB 的 base64,压在每个页面的关键路径上,同时还把一个家庭住址以谁也想不到去看的形式提交进了仓库。

先用 EXIF 查看与清除清掉它,那个工具只重写容器,画面碰都不碰,另有一篇指南讲它。图片转 Data URI 会读出 JPEG、PNG 或 WebP 里到底带了多少元数据,在你复制任何东西之前就告诉你。

拿到之后,该放哪儿

图只在一条规则里出现,那就把 URI 写在那条规则里。要是出现在不止一条里,而图标通常都不止,把悬停状态和深色主题一算就明白,那就声明成一个自定义属性,只声明一次:

:root {
  --icon-search: url("data:image/svg+xml,%3Csvg ... %3E");
}

.search-field { background-image: var(--icon-search); }
.search-button::before { content: var(--icon-search); }

一个 3 KB 的 URI 粘进四条规则,就是 12 KB 的样式表,外加图标一改要动四个地方。自定义属性两样都只有一份。而且这也是让主题切换成立的写法:在媒体查询里重新定义 --icon-search,所有用到它的地方跟着变。

要是用在 <img> 标签而不是 CSS 里,记得写上 width 和 height。内联图片是瞬间加载的,所以缺尺寸造成的布局抖动快到你看不见,却照样记在你账上。SVG 是例外:一个只带 viewBox 的 SVG 本来就没有自己的像素尺寸,把浏览器那个 ⁦300×150⁩ 的默认值写到标签上,等于给一张能任意缩放的图钉了一个谁也没选过的尺寸。

alt 留空,除非你真有一句实话可写。这张图到底有实际含义还是纯装饰,只有你知道;而拿文件名瞎猜一句描述,对用读屏软件的人来说,比没有描述更糟。

形态卡片:几个按钮决定输出什么,CSS 背景规则、img 标签或裸的 URI,另有一个 base64 与原样 SVG 的开关。
去处决定产出,所以先问清楚,而不是丢给你当成复制粘贴的练习。

什么时候答案是“别内联”

图片编码之后超过 50 KB 上下,内联就是选错了工具,编码上再怎么讲究也补不回来。替代办法,按值得一试的顺序:

这些事一样都不用上传

把一个文件编成 base64 就是做算术。用的是浏览器从一开始就带着的两个函数,btoa 和 encodeURIComponent,而一张图为了换个写法就跑一趟服务器再回来,技术上毫无理由。凡是要你上传文件才肯干这件事的转换器,都是为它自己的理由在上传,不是为你的。

这里的工具哪儿都不发:页面的 Content-Security-Policy 把它可能联的地址全列了出来,没有一个是本站的。与其听我们说,不如把页面打开,拔掉网线,照编一张试试。把文件传给在线转换器安全吗?里还有另外三个检查,用在任何工具上都管用。