如何优化 CSS 的渲染和加载性能

如何优化 CSS 的渲染和加载性能

结合现代网站的复杂性和浏览器处理 CSS 的方式,即使是中等数量的 CSS 也会成为人们处理有限的设备、网络延迟、带宽或数据限制的瓶颈。因为性能是用户体验的重要组成部分,所以必须确保跨各种形状和大小的设备提供一致的、高质量的体验,这也需要优化 CSS。

这篇文章将讨论 CSS 会导致什么样的性能问题,以及制作性能最佳的 CSS 文件。

CSS 是如何工作的?

当一个页面有 CSS 可用时,无论是内联样式表还是外部样式表,浏览器会延迟呈现,直到 CSS 被解析。这是因为没有 CSS 的页面通常是不可用的。如果浏览器显示的是一个没有 CSS 的混乱页面,然后几分钟后又切换到一个有样式的页面,变化的内容和突然的视觉变化会给用户带来混乱的体验。

尽管浏览器在完成 CSS 解析之前不会显示内容,但它会处理 HTML 的其余部分。但是,脚本会阻塞解析器,除非它们被标记为deferasync。脚本可能会操作页面和其余代码,因此浏览器必须小心该脚本何时执行。

因为脚本会影响应用于页面的样式,所以如果浏览器仍然在处理一些 CSS,它会等到处理完成后再运行脚本。因为在脚本运行之前它不会继续解析文档,这意味着 CSS 不再只是阻塞呈现 —— 取决于文档中外部样式表和脚本的顺序,也可能停止 HTML 解析。

查看 CSS 文件的大小

压缩和最小化 CSS 文件

建立连接来下载外部样式表不可避免地会导致延迟,但是可以通过最小化网络传输的总字节来加快下载速度。

压缩文件可以显著提高速度,许多托管平台和 cdn 默认使用压缩对资产进行编码 (或者你可以轻松地配置它们)。用于服务器和客户机交互的最广泛的压缩格式是 Gzip。还有 Brotli,它可以提供更好的压缩结果,尽管它不像 Gzip 那样受支持。

缩小是删除空白和任何不必要的代码的过程。输出是一个较小但完全有效的代码文件,浏览器可以解析它,这将为你节省一些字节。Terser 是一个流行的 JavaScript 压缩工具,如果你使用 webpack, v4 包含一个插件来创建缩小的构建文件。

移除未使用的 CSS 代码块

当使用 CSS 框架时,最终使用未使用的 CSS 是相对常见的 (除非我们只包括我们需要的组件)。在长时间增长的更大的代码库中也会出现同样的问题。

移除不使用的 CSS 通常需要手工操作。主要的挑战是它有多复杂。我们必须仔细审核整个网站,在所有可能的状态下,在所有可能的设备上 (包括媒体查询),并执行所有可能改变风格的 JavaScript 功能。UnusedCSS 和 PurifyCSS 是流行的工具,可以帮助确定不必要的样式,但我们应该将它们与仔细的视觉回归测试配对。

在 js 中使用 CSS 是一个显著的优势:在每个组件中呈现的样式只需要 CSS。快速 CSS-in- js 的秘密是内联的 CSS 到页面或提取它到外部 CSS 文件。将 CSS 发送到 JavaScript 文件中会导致解析和计算速度变慢。

优先使用关键的 CSS 代码

关键 CSS 是一种为折叠以上的内容提取和内联 CSS 的技术。在 HTML 文档的<head>中内联提取的样式可以消除额外请求获取这些样式的需要,并加快呈现速度。

为了减少首次渲染的往返次数,请将折叠以上的内容保持在 14kb (压缩) 以下。

确定关键的 CSS 不是完全准确的,因为你需要对折叠位置进行假设 (折叠位置因设备屏幕大小而异)。对于高度动态的站点来说,这可能很困难。即使不精确,它仍然可以带来性能改进,我们可以使用 Critical、CriticalCSS 和 Penthouse 等工具将其自动化。

异步加载 CSS

CSS 的其余部分 (不太关键的部分) 最好异步加载。实现这一点的方法是将 linkmedia 属性设置为 print

<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">

“Print” 媒体类型定义了当用户尝试打印页面时的样式表规则,浏览器将加载这样的样式表而不会延迟页面呈现。将该样式表应用于所有媒体 (即屏幕,而不仅仅是打印),使用 onload 属性在样式表加载完成时将media设置为all

另一种选择是使用<link rel="preload"> (而不是 rel="stylesheet") 来实现类似于上面的模式,并在加载事件时将 rel 属性切换为stylesheet。在使用这种方法时,有一些缺点需要考虑。

浏览器对preload的支持还不是很好,所以一个 polyfill (或者使用 loadCSS 之类的库) 是跨浏览器应用样式表的必要条件。 预加载很早就获取文件,具有最高的优先级,这可能会使其他重要的下载优先级降低。

如果你想要 preload 提供的高优先级 fetch (在支持它的浏览器中),loadCSS 的创建者建议你把它和第一种模式结合起来,像这样:

<link rel="preload" href="/path/to/my.css" as="style">
<link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">

在 CSS 文件中避免使用 @import

在 CSS 文件中使用 @import 会降低渲染速度。首先,浏览器必须下载 CSS 文件来发现导入的资源,然后在呈现资源之前发起另一个下载请求。

如果你有一个包含 @import url (imported.css); 的样式表网络瀑布是这样的:

使用 import 时的网络瀑布视图

link 元素中加载两个样式表允许并行下载:

使用 link 时的网络瀑布视图

使用有效的 CSS 动画效果

当你在页面上设置动画元素时,浏览器经常需要重新计算它们在文档中的位置和大小,这将触发布局。例如,如果更改元素的宽度,它的任何子元素都可能受到影响,并且页面布局的大部分可能会更改。布局几乎总是局限于整个文档,所以布局树越大,它执行布局计算的时间就越长。

在动画元素时,尽量减少布局和重绘是至关重要的。并不是所有的 CSS 动画技术都是相同的,现代的浏览器可以用position, scale, rotation, 和 opacity最好地创建性能动画:

  • 不要改变heightwidth属性,使用 transform: scale()
  • 要移动元素,应避免更改topleftrightleft属性,而使用 transform: translate()。
  • 如果你想模糊背景,可以考虑使用模糊图像并改变其不透明度。

contain 属性

contain CSS 属性告诉浏览器,元素及其后代被认为 (尽可能地) 独立于文档树。它将页面的一个子树与其他子树隔离开来。然后,浏览器可以优化页面独立部分的呈现 (样式、布局和绘制操作),以提高性能。

contain 属性对于包含许多独立小部件的页面非常有用。我们可以使用它来防止每个小部件内的更改在小部件的边界框之外产生副作用。大多数静态站点从这种策略中获益甚微。

用 CSS 优化字体加载

在字体加载时避免文本不可见

字体通常是需要一段时间才能加载的大文件。一些浏览器隐藏文本,直到字体加载 (导致 “闪光看不见的文本” 或 FOIT) 来处理这个问题。当优化速度时,你会想要避免 “闪过不可见的文本”,并立即使用系统字体 (预先安装在他们的机器上) 向人们显示内容。一旦字体文件已经加载,它将取代系统字体称为 “闪光的无样式文本” 或 FOUT。

实现这一点的一种方法是使用 font-display—— 一种用于指定字体显示策略的 API。使用 font-displayswap告诉浏览器,使用这种字体的文本应该立即使用系统字体显示。

使用可变字体来减小文件大小

可变字体可以将字体的许多不同变体合并到单个文件中,而不是为每个宽度、粗细或样式使用单独的字体文件。它们允许你使用 CSS 和一个 @font-face 引用访问给定字体文件中的所有变体。

当你需要多种字体变体时,可变字体可以显著减小文件大小。你可以加载包含所有信息的单个文件,而不是加载常规和粗体样式及其斜体版本。

Monotype 进行了一项实验,将 12 种输入字体组合在一起,生成了横跨三种宽度、横跨斜体和罗马体的 8 种权重。在一个可变字体文件中存储 48 种字体意味着文件大小减少 88%。

不要担心 CSS 选择器的速度

CSS 选择器的结构会影响浏览器匹配它们的速度。浏览器从右到左读取选择器,所以当你使用后代选择器时。例如,nav a {},它将首先匹配页面上的每个<a>元素,然后归零在 nav 内部的一个。如果你使用一个更具体的选择器,例如,在 nav 元素中的每个<a>上使用.nav-link,它就不会花时间去匹配页面上的每个<a>

如果你考虑浏览器如何匹配从右到左的选择器和一个例子,比如.container ul li a {},你就会明白为什么后代选择器经常被标记为 “昂贵的”。

这样的选择器似乎是一个速度问题。然而,选择器匹配性能是快速的。CSS 声明对压缩算法非常友好,因此优化 CSS 选择器所需的工作通常最好花在应用程序的其他部分,这样投资回报会更高。

CSS 对于加载页面和用户体验至关重要。虽然我们通常会优先考虑其他更有影响力的资产 (如脚本或图像),但我们不应该忘记 CSS。使用上面描述的策略,你将能够确保快速交付和执行。