原文链接:https://csswizardry.com/2017/02/base64-encoding-and-performance/
作者:Harry Roberts
来源:CSS Wizardry
本文一共分为两篇。阅读第 2 部分。
几年前我们只是单纯的考虑,“减少请求数量”,使页面加载速度更快。虽然这是一个合理化的建议,但实际上,我们还可以通过合理的分配资源的请求数,来达到我们的目的。
Base64编码就是为了减少请求数,而出现的“最佳实践”。例如:将一张图片编译为文本资源,嵌入到样式表中。减少了请求数,同时也展示了图片资源。不可想象是么?
不。
不幸的是,Base64编码资源是一种非常反面的教材1。在这篇文章中,我会分享一些关于关键路径优化,Gzip,当然,还有对于Base64编码的看法。
让我们看一些代码
我写这篇文章的动机是因为我刚好为客户做一个性能审计,我遇到了我描述的问题。这是来自实际客户端的实际样式表:示例是匿名的,但这是一个完全真实的项目。
写这篇文章的起因是,我最近正在为客户开发一个性能检测,正好遇到了这个问题。这是一个匿名的真实案例。
我打开了一个网页,查看了网络配置, 发现了一个CSS
文件(因为我们不想看到12个样式表的请求链接,所以通常会统一到一个文件中),在不压缩的情况下,竟然达到了925k
。线上的文件字节数大大减少,还是接近232K
大小。
样式表文件竟然这么大?我其实都不用看,也知道里面一定有一些Base64编码格式。我不是说文件大一定是因为Base64的原因(可能是因为插件,代码组织混乱,历史原因等),但文件大通常都是因为Base64。
- 不管是不是Base64,925K的CSS都是惊人的。
- 压缩后也只是减少到759K
- Gzipping 使得降到232K。相同的代码,减少了将近693K。
- 232K 对于线上文件来说,还是很大。
直勾勾的盯了屏幕88ms,只是为了解析那个大小的样式文件。
让它通过网络加载,才是我们遇到的问题。
我美化了文件2,保存到本地,执行了CSSO,然后通过 Gzip 运行它定时输出的常规设置。如下:
harryroberts in ~/Sites/<client>/review/code on (master)
» csso base64.css base64.min.css
harryroberts in ~/Sites/<client>/review/code on (master)
» gzip -k base64.min.css
harryroberts in ~/Sites/<client>/review/code on (master)
» ls -lh
total 3840
-rw-r--r-- 1 harryroberts staff 925K 10 Feb 11:23 base64.css
-rw-r--r-- 1 harryroberts staff 759K 10 Feb 11:24 base64.min.css
-rw-r--r-- 1 harryroberts staff 232K 10 Feb 11:24 base64.min.css.gz
接下来要做的是看看这些字节中有多少是来自 Base64 编码文件。为了解决这个问题,我简单地(粗略地)删除了包含data:
字符串的所有行 / 声明(对于 Vim 用户来说是:g/data:/d3
3)。Base64 编码大部分用于图像 / 精灵和几种字体。然后我将这个文件保存为no-base64.css
,并做了相同的缩小,和Gzip 压缩:
harryroberts in ~/Sites/<client>/review/code on (master)
» ls -lh
total 2648
-rw-r--r-- 1 harryroberts staff 708K 10 Feb 15:54 no-base64.css
-rw-r--r-- 1 harryroberts staff 543K 10 Feb 15:54 no-base64.min.css
-rw-r--r-- 1 harryroberts staff 68K 10 Feb 15:54 no-base64.min.css.gz
在我们未压缩的 CSS 中,消失了整整217K的 Base64。仍然是很大的 CSS量(708K 是相当不方便),但我们已经想办法摆脱了23.45%的代码。
接下来真正让我们惊讶的是,Gzip压缩后,我们的线上代码从 708K 降到了 68K! 哇,这节省了 90.39%!
Gzip 保存…
Gzip 真不可思议!这可能是最好的方式从开发者的角度来保护用户。通过在线压缩,节约了90%的CSS代码。毫不费力的从 708K 下降到 68K。
… 有时
但是,这是 Gzip 的非 Base64 编码版本。我们看看原始的 CSS(使用 Base64 编码的 CSS),我们发现我们只节省了 74.91%。
Base64 | 毛尺寸 | 压缩大小 | 保存 |
---|---|---|---|
是 | 925K | 232K | 74.91% |
没有 | 708K | 68K | 90.39% |
两个文件之间的差异是大概在 164K(70.68%)。我们可以通过将这些资源移动到更合适的地方,减少 164K 的 CSS。
Base64 压缩非常不好。下次有人尝试,可以告诉他们Gzip这点(如果他们试图证明 Base64更好)。
那么为什么 Base64 这么糟?
好吧,所以我们很清楚,现在 Base64 增加文件大小的方式,Gzip 无法真正帮助我们,但这只是问题的一小部分。我们为什么这么害怕文件大小的增加?单个图像可能大小就超过 232K,所以我们是不是从那里开始研究更好?
不错的问题,我很高兴你提到了图像…
我们需要谈论图像
为了理解 Base64 是多么糟糕,我们首先需要了解好的 图像是什么。争议点:图像不像你认为的性能那么差。
确实,图像是一个问题。事实上,他们是页面膨胀的第一贡献者。截至 2016 年 12 月 2 日,图片占平均网页的 1623K(约 65.46%)。这使得232K 样式表如沧海一粟。但是,浏览器处理图片和样式表的方式有着根本区别:
图像不阻止渲染; 样式表阻止渲染。
浏览器渲染页面,不会等待图片加载完成,甚至说,图片加载失败,页面也会渲染。图片不是关键资源,即使很大,也没有关系。
另一方面,CSS 是一个关键资源。在构建渲染树之前,浏览器无法开始渲染页面。浏览器在构造 CSSOM 之前不能构造渲染树。在样式表没有解压,解析,加载完成之前,他们不能构造 CSSOM。CSS 是瓶颈。
现在你应该可以明白为什么我们这么害怕的 CSS 字节数:他们延迟了页面渲染,展示给用户一个空白网页。希望你现在可以意识到把 Base64 编码图像加到你的 CSS 文件中是多么的讽刺:你只是把数百千字节的非阻塞资源转换为阻塞资源去追求性能。原本不需要这些资源加载完毕,就可以展示页面,但现在他们被迫走上了关键资源的道路。这并不意味着图像更早到达; 而是意味着关键资源 CSS 要稍后到达。真的这么糟糕吗?
恩,是的。
浏览器是很聪明的。真的很聪明。他为我们做了很多性能优化,他们知道如何更好(多总比没有好)。下面我们看下响应式:
.masthead {
background-image: url(masthead-small.jpg);
}
@media screen and (min-width: 45em) {
.masthead {
background-image: url(masthead-medium.jpg);
}
}
@media screen and (min-width: 80em) {
.masthead {
background-image: url(masthead-large.jpg);
}
}
我们给浏览器三个可能会用到的图片,但它只会下载其中一个。它需要哪一个,获取那一个,其他两个并不加载。
但一旦我们 Base64 这些图像,三张图片的字节数都会被下载,相当于我们有了三倍(或左右)的开销。这里是实际项目中用到的CSS代码块(没有使用base64,完整的代码片段Gzip之前总共 26K,之后18K):
@media only screen and (-moz-min-device-pixel-ratio: 2),
only screen and (-o-min-device-pixel-ratio:2/1),
only screen and (-webkit-min-device-pixel-ratio:2),
only screen and (min-device-pixel-ratio:2),
only screen and (min-resolution:2dppx),
only screen and (min-resolution:192dpi) {
.social-icons {
background-image:url("data:image/png;base64,...");
background-size: 165px 276px;
}
.sprite.weather {
background-image: url("data:image/png;base64,...");
background-size: 260px 28px;
}
.menu-icons {
background-image: url("data:image/png;base64,...");
background-size: 200px 276px;
}
}
所有用户,无论是否是在视网膜屏上(甚至用户使用的是不支持媒体查询的浏览器),在浏览器器未展示页面之前,都将被迫下载额外的 18K CSS。
Base64的资源总是被下载,哪怕完全用不上。相较于浪费资源,它更大的缺点是阻塞渲染。
我们需要谈论字体
到目前为止我只提到了图像,但字体除了一些在浏览器如何处理 无样式的字体 / 看不见文本的闪烁问题(FOUT 或 FOIT)上有细微区别外,几乎完全相同。这个项目中的字体未压缩的 CSS总共 166K (124K Gzipped(依旧很大))。
如果不是一篇文章,字体也不算不上是关键资源,注意:你的页面没有字体也可以渲染。问题是,浏览器处理 web 字体各不相同:
- Chrome 和 Firefox 最多只能显示 3 秒钟内显示的文字。如果网络字体在这三秒内到达,文本从不可见切换到您的自定义字体。如果字体在 3 秒后仍未到达,则文本使用系统默认字体。这是 FOIT。
- IE 会立即显示系统备用字体,然后在自定义字体到达时将进行替换。这是 FOUT。在我看来,这是最优雅的解决方案。
- Safari 会不显示字,直到字体到达。如果字体从未到达,它也没有备用字体。这是 FOIT。这绝对是可憎的。您的用户将永远无法看到您网页上的任何文字。
为了解决这个问题,人们开始用 Base64 将他们的字体内嵌到样式表:如果 CSS 和字体完全同时到达,那么就不会出现 FOIT 或 FOUT,因为 CSSOM 的渲染和字体几乎同时开始。
和上面的一样,字体移到关键路径,不会加快他们的交付,延迟了您的 CSS。有一些非常聪明的字体加载解决方案, 但 Base64 不是其中之一。
我们需要谈论缓存
Base64 还影响我们拥有更复杂的缓存策略:将我们的字体,图像和样式联系在一起,会受同样的规则约束。这意味着如果我们在 CSS 中只改变一个十六进制值 - 六个字节的新数据的更改 - 我们必须重新下载几百 KB
的样式,图像和字体。
事实上,在这里字体是罪魁祸首:字体是改动的频率极低。另一个开发者和我一起运行一个长期的项目:CSS 最后一次修改时间是昨天; 字体文件的最后一次修改时间是八个月前。想象一下,每当您更新样式表中的任何内容时,强制用户重新下载这些字体。
Base64 编码意味着我们不能根据它们的更新频率单独地缓存文件,意味着无论什么时候有更改,都要重新缓存不相干的文件。这太浪费资源了。
基本分离的关注点:字体的缓存不应该依赖于图像缓存,不应该依赖于样式的缓存。
好吧,让我们快速回顾一下:
Base64 编码增加了文件大小,并且无法有效压缩(例如 Gzip)。文件大小的增加会延迟渲染,发生在渲染阻塞资源。
Base64 编码强制非关键资源在关键路径上加载。(例如图像,字体)这意味着,在这种特殊情况下,我们不是现在 68K 的CSS 文件,而是下载了3.4倍文件大小。我们在让用户等待他们原本不需要等待的资源加载!
Base64 编码强制下载所有资源字节数,即使不使用。这纯属浪费资源,并且,它发生在我们的关键路径上。
Base64 编码限制了我们单独缓存资源; 图像和字体现在与样式的缓存规则一致,反之亦然。
总而言之,这是一个非常不好的情况:请避免 Base64。
数据会话
所有这篇文章所写的都是我目前已知的东西。我没有用任何数据去进行测试:只是展示了浏览器的工作方式。接下来,我决定去做一些测试,用事实和数据说话。转到第 2 部分阅读更多。