优化与否

在开始优化 CSS 之前,你应该回答一个问题:“我需要优化什么?”下面讨论的一些技巧和技术是适用于几乎任何 Web 项目的良好实践,然而另一些只在特定情况下需要。试图在所有地方应用这些技术可能是不必要的,而且可能会浪费你的时间。你应该确定每个项目实际上需要哪些性能优化。

为了做到这一点,你需要测量网站性能。正如上面的链接所示,有几种不同的测量性能的方法,其中一些涉及复杂的性能 API。然而,入门的最佳方法是学习如何使用内置的浏览器网络和性能工具,以查看页面加载中哪些部分花费了很长时间,并且需要进行优化。

优化渲染

浏览器有一个固定的渲染流程——只有在布局(layout)完成后才能绘制(paint)页面,而布局的前提是要生成渲染树(render tree),而渲染树的生成则需要 DOM 和 CSSOM 树的配合。

如果先让用户看到一个没有样式的页面,等 CSS 样式解析完后再重绘(repaint),这样的体验会很差。所以,浏览器会等到确定需要 CSS 时才开始渲染。只有在下载完 CSS 并生成 CSS 对象模型 CSS 对象模型(CSSOM)之后,浏览器才会绘制页面。

为了优化 CSSOM 的构建并提高页面性能,你可以根据当前 CSS 的状态执行以下一项或多项操作:

删除不必要的样式:这听起来很明显,但你可能不相信,很多开发者都会忘记清除在开发过程中添加的不会被用到的 CSS 规则。所有脚本都会被解析,无论它在布局于绘制时是否被使用,因此删除无用样式可以加速网页渲染。正如如何从网站中删除未使用的 CSS?(csstricks.com,2019)总结的那样,对于大型代码库来说,这是一个难题,没有灵丹妙药可以可靠地查找和删除未使用的 CSS。你需要尽量保持 CSS 的模块化,并在添加和删除时谨慎小心。

将 CSS 拆分为独立模块:CSS 模块化可以延迟加载在页面加载阶段非必要的 CSS,缩短初始 CSS 的阻塞和加载时间。最简单的方法是将 CSS 拆分为独立的文件,并仅加载所需内容:

html

rel="stylesheet"

href="mobile.css"

media="screen and (max-width: 480px)" />

上面的示例提供了三组样式——始终会加载的默认样式、只有在打印文档时才会加载的样式,以及仅在窄屏设备上加载的样式。默认情况下,浏览器会假设每个指定的样式表都会阻塞渲染。你可以通过添加包含媒体查询的 media 属性来告诉浏览器何时应用样式表。当浏览器看到只需要在特定情况下应用的样式表时,它仍然会下载样式表,但不会阻塞渲染。通过将 CSS 拆分为多个文件,主要的渲染阻塞文件(在本例中为 styles.css)会小得多,从而减少渲染阻塞的时间。

最小化和压缩你的 CSS:最小化包括在代码进入生产环境后,在文件中删除仅为了人类可读性而存在的所有空格。通过对 CSS 进行最小化,你可以大大减少加载时间。通常,最小化是作为构建过程的一部分进行的(例如,大多数 JavaScript 框架在构建项目准备部署时会对代码进行最小化)。除了最小化之外,还要确保你的站点所在的服务器在提供文件之前对文件使用诸如 gzip 的压缩。

简化选择器:人们经常编写比应用所需样式更复杂的选择器。这不仅会增加文件大小,还会增加解析这些选择器的时间。例如:

css/* 非常具体的选择器 */

body div#main-content article.post h2.headline {

font-size: 24px;

}

/* 你可能只需要这个 */

.headline {

font-size: 24px;

}

将选择器简化和降低优先级对于维护也是有好处的。简单选择器的作用很容易理解,如果选择器不具有那么高的优先级,以后需要时就很容易覆盖样式。

不要将样式应用于不需要的元素:常见的错误是使用通用选择器将样式应用于所有元素,或者至少应用于比实际需要的元素更多的元素。这种类型的样式会对性能产生负面影响,特别是在较大的站点上。

css/* 选择 元素内的所有元素 */

body * {

font-size: 14px;

display: flex;

}

请记住,许多属性(例如 font-size)会从其父元素继承其值,因此你不需要在所有地方应用它们。而且功能强大的工具(如弹性盒子)需要谨慎使用。在所有地方都使用它们可能会导致各种意外行为。

使用 CSS 精灵图减少图像相关的 HTTP 请求:CSS 精灵图 是一种技术,它将你希望在站点上使用的多个小图像(例如图标)放入单个图像文件中,然后使用不同的 background-position 值在不同的位置显示图像的一部分。这可以大大减少获取图像所需的 HTTP 请求数量。

预加载重要资源:你可以使用 rel="preload" 将 元素转换为预加载器,用于关键资源,包括 CSS 文件、字体和图片:

html

rel="preload"

href="ComicSans.woff2"

as="font"

type="font/woff2"

crossorigin />

rel="preload"

href="bg-image-wide.png"

as="image"

media="(min-width: 601px)" />

使用 preload,浏览器会尽快获取引用的资源,并将其存储在浏览器缓存中,以便在后续代码中引用时可以更快地使用它们。为了让用户体验尽可能流畅,我们应提前加载页面加载初期用户会遇到的高优先级资源。请注意,你还可以使用 media 属性创建响应式的预加载器。

另请参阅预加载重要资源以提升加载速度这篇 web.developers.google.cn 上的文章(2020)。

处理动画

动画可以改善感知性能,使界面更加流畅,让用户在等待页面加载时感觉到进展(例如加载旋转图标)。然而,更大更多的动画自然需要更多的处理能力来处理,这可能会降低性能。

最简单的建议是减少所有不必要的动画。你还可以为用户提供一个控件/站点选项,让他们可以关闭动画,例如当他们使用低功率设备或电池电量有限的移动设备时。你还可以使用 JavaScript 来控制页面是否应用动画。还有一个名为 prefers-reduced-motion 的媒体查询,可以根据用户对动画的操作系统级偏好选择性地提供动画样式。

对于必要的 DOM 动画,建议尽可能使用 CSS 动画,而不是 JavaScript 动画(Web 动画 API 提供了一种直接使用 JavaScript 连接到 CSS 动画的方法)。

选择要进行动画处理的属性

接下来,动画性能在很大程度上取决于你要进行动画处理的属性。某些属性在进行动画处理时会触发回流(进一步也会触发重绘),应该避免使用这些属性。这些属性包括:

修改元素的尺寸,例如 width、height、border 和 padding。

重新定位元素,例如 margin、top、bottom、left 和 right。

更改元素的布局,例如 align-content、align-items 和 flex。

添加改变元素几何形状的视觉效果,例如 box-shadow。

现代浏览器很智能,只会重新绘制文档中已更改的区域,而不是整个页面。因此,越大的动画成本越高。

如果可以的话,最好对不会引起回流/重绘的属性进行动画处理。这包括:

变换

opacity

filter

在 GPU 上进行动画处理

为了进一步提高性能,你应该考虑将动画处理工作转移到主线程之外,并放到设备的 GPU 上进行(也称为合成(compositing))。这可以通过选择特定类型的动画来实现,浏览器会自动将这些动画发送到 GPU 来处理。包括:

3D 变换动画,例如 transform: translateZ() 和 rotate3d()。

具有某些其他属性动画的元素,例如 position: fixed。

应用了 will-change 的元素(请参阅下面的小节)。

特定的在其自己层中渲染的元素,包括