这个结论我一开始不信。
我手头有件事要做:把图片全用 base64 塞进 HTML,做成一个可以整体带走、粘进编辑器不掉图的文件。塞之前我想先知道代价,就顺手测了一遍。
第一版数字很难看。
120 张照片,明文大了 33.33%。
然后我开了 gzip,按能比的口径重算一次。
差距塌到 0.31%。
两个数差了 100 倍。
中间差的不是运气,是没人愿意多算的那一步。
测试怎么搭的
样本是 120 张真实照片,来自本机正在用的配图目录,不是找来的测试图。原图合计 17,902,030 字节。
造两份 HTML。图片数量、位置、宽高、图注完全一样,唯一区别是 src:
外链版写 src=「/uploads/kepu/xxx.jpg」。
内嵌版写 src=「data:image/jpeg;base64,....」。
然后算一件事:访问这个页面,服务器一共要吐多少字节。
外链版 = gzip(HTML) + 120 张原图。
内嵌版 = gzip(整页 HTML,图都在里面)。
只比 HTML 明文是耍流氓,因为外链版的图片根本不在 HTML 里,那个数没法当成本。
33.33% 是真的

base64 的膨胀率精确等于 4/3,也就是 33.33%。这是编码本身决定的,没有优化空间。
17,902,030 字节的原图,base64 后 23,869,528 字节。一个字节都不差,正好 4/3。
所以那个流传的说法不是谣言。
它只是没算完。
开 gzip 之后

外链版总共 17,903,266 字节。
内嵌版 17,959,257 字节。
多 55,991 字节,0.31%。
看起来像作弊,其实不是。我核对了一下原因:
JPEG 本身已经压过,gzip 对它只有 0.57% 的收益(17,902,030 变 17,799,730)。
base64 不一样,它能压到 75.19%(23,869,528 变 17,947,334),正好把那 33% 还回来。
至于为什么 base64 这么能压,我的推测是它的字符集只有 64 个符号,每 3 字节映射到 4 字符时高位模式很规整,zlib 容易吃。这条我没验证过,只是 75.19% 太接近 3/4,看着像巧合。
规模越小,内嵌越亏一点
我按 1 张、5 张、20 张、120 张分别算了一遍。
1 张图时内嵌多 0.79%。
5 张多 0.82%。
20 张多 0.71%。
120 张只多 0.31%。
方向很一致:块越大,gzip 压得越好,内嵌越不吃亏。
反过来说,「页面里就一两张小图」这个场景,内嵌相对更亏,虽然绝对值都不大。
真正的账在第二次

这条跟 gzip 一点关系都没有。
外链的图片能被浏览器缓存。
第二次打开走 304,一张图都不用重下,20 张图加起来 6,300 字节。
内嵌的图在 HTML 里。
HTML 一重下,图跟着重下,3,395,655 字节。
539 倍。
首屏也是同一个道理:内嵌必须把整页收完,浏览器才解析到 <img>。外链可以一边收 HTML 一边并行拉图。
这里要标清楚:首屏那两个数(4G 0.08s 对 2.16s,弱网 0.87s 对 22.11s)是我按带宽假设算出来的,不是拿浏览器实测的。 假设 4G 1.5MB/s、弱网 150KB/s、单线程。
真实浏览器会并行下载、会流式解析,内嵌的实际表现应该比 22.11s 好看一些。我留着这个数,是因为方向没错,绝对值别当真。
还有一个代价:gzip 是服务端的活。整页 23MB 的 HTML,gzip-9 要 1,235 毫秒 CPU。120 张图时才这样,20 张是 230 毫秒,1 张是 10 毫秒。
那我给你做的那个 653KB 文件白做了吗
不算白做,但理由跟上面的账无关。
那个文件的用法是:本地双击打开,Ctrl+A,Ctrl+C,粘到编辑器里。
全程不走网络。
0.31% 和 539 倍,都是网络传输的口径,在这个场景里不成立。
所以同一张图要不要塞进 HTML,取决于它要去哪。
上网络,就让浏览器缓存它;进剪贴板,就把它一起带上。
我没测的部分
brotli 本机没装,跑不了。所以上面全部是 gzip-9 的结果。brotli 对文本通常比 gzip 好 15% 到 20%,但对 base64 效果如何,我没有数据【本人未实测】。
HTTP/2、CDN 的图片转码、真实浏览器的首屏耗时,这几个都没测【暂无相关资料】。
还有一个我没想清楚的地方:如果页面只被打开一次,内嵌其实更优,因为省了建立连接的往返。但「只被打开一次」这件事,事前没法知道。
要那张 120 张图的完整数据表和复现脚本的话,说一声,我整理出来。