深入了解CSS和网络性能

本篇文章带大家了解一下CSS和网络性能。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

深入了解CSS和网络性能

挺长的一篇文章,比较全面地介绍了 CSS 加载的相关知识,由于译者水平有限,有能力的同学建议直接看原文,同时也希望译文对你有所帮助,谢谢~以下是正文:


承蒙抬爱,我被称为 CSS 魔术师已经十多年了,但最近在博客上,CSS 相关的文章却不多。那就结合 CSS 与性能这两大主题,为大家带来一篇文章吧。

CSS 是页面渲染的关键因素之一,(当页面存在外链 CSS 时,)浏览器会等待全部的 CSS 下载及解析完成后再渲染页面。关键路径上的任何延迟都会影响首屏时间,因而我们需要尽快地将 CSS 传输到用户的设备,否则,(在页面渲染之前,)用户只能看到一个空白的屏幕。

最大的问题是什么?

广义而言,CSS 是(渲染)性能的关键,这是由于:

  • 浏览器直到渲染树构建完成后才会渲染页面;

  • 渲染树由 DOM 与 CSSOM 组合而成;

  • DOM 是 HTML 加上(同步)阻塞的 JavaScript 操作(DOM 后的)结果;

  • CSSOM 是 CSS 规则应用于 DOM 后的结果;

  • 使 JavaScript 非阻塞非常简单,添加 async 或 defer 属性即可;

  • 相对而言,要让 CSS 变为异步加载是比较困难的;

  • 所以记住这条经验法则:(理想情况下,)最慢样式表的下载时间决定了页面渲染的时间

基于上述考虑,我们需要尽快构建 DOM 与 CSSOM。一般情况下,DOM 的构建是相对较快,(当请求某个页面时,)服务器响应的首个请求是 HTML 文档。但一般 CSS 是作为 HTML 的子资源而存在,因此 CSSOM 的构建通常需要更长的时间。

在这篇文章中,会讲述 CSS 为何是网络瓶颈(无论是对于它自己或是其他资源),该如何突破它,从而缩短关键路径以减少首次渲染前的等待时间。

使用关键 CSS

如果条件允许,缩短渲染前等待时间最有效的方式就是使用 Critical CSS (关键 CSS)模式:找出首次渲染所需的样式(通常是首屏相关的样式),将它们内联到 标签中,其他样式则通过异步的方式进行加载。

虽然这十分有效,但实施起来却并不容易,比如:高度动态化的网站(译者注:如 SPA)通常难以提取首屏相关的样式、提取的过程需要自动化、需要对首屏不同元素显示或隐藏的状态作出假设、某些边界情况难以处理以及相关工具仍未成熟等问题。如果你的项目相当庞大或是有历史包袱,这将变得更为复杂。

根据媒体类型拆分代码

如果在项目组难以执行关键 CSS 策略,可以尝试根据媒体查询拆分 CSS 文件,这也是一种可靠的策略。执行此策略后,浏览器表现如下:

  • 以非常高的优先级下载符合当前上下文(设备、屏幕尺寸、分辨率、方向等)的 CSS 文件,阻塞关键路径;
  • 以非常低的优先级下载不符合当前上下文的 CSS 文件,不会阻塞关键路径。

浏览器基本上能将未命中媒体查询的 CSS 文件延迟下载。

<link rel="stylesheet" href="all.css" />

如果我们把全部的 CSS 代码都放在一个文件中,请求的表现如下:

深入了解CSS和网络性能

我们可以观察到,这个单独的 CSS 文件会以 最高 的优先级下载。

根据媒体查询拆分成若干个 CSS 文件后:

<link rel="stylesheet" href="all.css" media="all" />
<link rel="stylesheet" href="small.css" media="(min-width: 20em)" />
<link rel="stylesheet" href="medium.css" media="(min-width: 64em)" />
<link rel="stylesheet" href="large.css" media="(min-width: 90em)" />
<link rel="stylesheet" href="extra-large.css" media="(min-width: 120em)" />
<link rel="stylesheet" href="print.css" media="print" />

浏览器会以不同的优先级下载 CSS 文件:

不符合当前上下文的 CSS 文件将以 _最低_ 优先级进行下载。

浏览器仍然会下载全部的 CSS 文件,但只有符合当前上下文的 CSS 文件会阻塞渲染。

避免在 CSS 文件中使用 @import

为缩短渲染等待时间而努力的下一项任务非常简单:避免在 CSS 文件中使用 @import

如果了解 @import 的原理,那应该清楚它的性能并不高,使用它会阻塞渲染更长时间。这是因为我们在关键路径上创造了更多(队列式)的网络请求:

  • 下载 HTML;

  • 请求并下载依赖的 CSS

    • (下载及解析完成后,本该是构造渲染树,然而;)
  • CSS 依赖了其他的 CSS,继续请求并下载 CSS 文件;

  • 构造渲染树。

以下是相关的案例:

<link rel="stylesheet" href="all.css" />

all.css 的内容:

@import url(imported.css);

最终,浏览器的请求瀑布图呈现为:

深入了解CSS和网络性能

关键路径上的 CSS 文件并没有并行下载。

通过将 @imports 请求的文件改为

<link rel="stylesheet" href="all.css" />
<link rel="stylesheet" href="imported.css" />

可以提高网络性能:

深入了解CSS和网络性能

关键路径上的 CSS 文件是并行下载的。

注意,有一个特殊的情况值得讨论。如果你没有包含 @importCSS 文件的修改权限,为了让浏览器并行下载 CSS 文件,可以往 HTML 中补充相应的 。浏览器会并行下载相应的 CSS 文件且不会重复下载 @import 引用的文件。

在 HTML 中谨慎地使用 @import

本节的内容比较奇怪。各大浏览器的相关实现上似乎都有问题,我以前提交了相关的bugs(译者注:简单说,当页面中存在:,浏览器不会并行下载,但加上引号后:,浏览器会并行下载)。

为了透彻地理解本节的内容,首先我们需要了解浏览器的预加载扫描器:各大浏览器都实现了一个名为预加载扫描器的辅助解析器。浏览器的核心解析器主要用于构建 DOM、CSSOM、运行 JavaScript 等。HTML 文档中某些标签与状态会阻塞核心解析器,因而核心解析器的运行是断断续续的。而预加载扫描器可以跳到核心解析器尚未解析的部分,用以发现其他待引用的子资源(如 CSS、JS 文件、图片等)。一旦发现此类子资源,预加载扫描器会开始下载它们,以便核心解析器在解析到对应内容时就能使用它们(,而不是直到那一刻才开始下载该资源)。预加载扫描器的出现,使网页的加载性能提高了19%,这是一项了不起的成就,可以极大地优化用户体验。

作为开发者,需要警惕预加载扫描器背后隐藏的问题,这在后文会进行阐述。

在 HTML 中使用 @import,在以 WebKit 与 Blink 为内核的浏览器中,可能会触发它们预加载扫描器的 bug,在 Firefox 与 IE/Edge 中,则表现低效。

Firefox 与 IE / Edge:在 HTML 中将 @import 放在 JS 和 CSS 之前

在 Firefox 与 IE/Edge 中,预加载扫描器不会并行下载