CSS 性能优化
在开发网站时,您需要考虑浏览器如何处理您网站上的 CSS。为了缓解 CSS 可能引起的任何性能问题,您应该对其进行优化。例如,您应该优化 CSS 以缓解渲染阻塞并尽量减少所需的重排次数。本文将引导您了解关键的 CSS 性能优化技术。
预备知识 | 已安装基本软件,并具备 客户端 Web 技术 的基础知识。 |
---|---|
目标 | 了解 CSS 对网站性能的影响以及如何优化 CSS 以提高性能。 |
优化还是不优化
在开始优化 CSS 之前,您应该回答的第一个问题是“我需要优化什么?”。下面讨论的一些技巧和技术是良好的实践,几乎适用于任何 Web 项目,而另一些则仅在某些情况下需要。试图将所有这些技术应用到所有地方可能是不必要的,并且可能会浪费您的时间。您应该弄清楚每个项目实际需要哪些性能优化。
为此,您需要测量您网站的性能。如上一个链接所示,有几种不同的方法来测量性能,其中一些涉及复杂的性能 API。然而,最好的入门方法是学习如何使用内置浏览器网络和性能工具等工具,查看页面加载的哪些部分花费了很长时间并需要优化。
优化渲染
浏览器遵循特定的渲染路径——绘制只发生在布局之后,而布局发生在渲染树创建之后,渲染树反过来又需要 DOM 和 CSSOM 树。
向用户显示未设置样式的页面,然后在解析 CSS 样式后重新绘制,这将是一种糟糕的用户体验。因此,CSS 在浏览器确定需要 CSS 之前会阻塞渲染。浏览器在下载 CSS 并构建 CSS 对象模型 (CSSOM) 后才能绘制页面。
为了优化 CSSOM 构建并提高页面性能,您可以根据 CSS 的当前状态执行以下一项或多项操作:
-
删除不必要的样式:这听起来很明显,但令人惊讶的是,有多少开发人员忘记清理在开发过程中添加到样式表中最终未使用的 CSS 规则。所有样式都会被解析,无论它们是否在布局和绘制期间使用,因此删除未使用的样式可以加快页面渲染速度。正如 如何从网站删除未使用的 CSS? (csstricks.com, 2019) 所总结的,对于大型代码库来说,这是一个难以解决的问题,并且没有可靠地查找和删除未使用的 CSS 的灵丹妙药。您需要努力保持 CSS 模块化,并谨慎、有意识地添加和删除内容。
-
将 CSS 拆分为单独的模块:保持 CSS 模块化意味着在页面加载时不需要的 CSS 可以在以后加载,从而减少初始 CSS 渲染阻塞和加载时间。最简单的方法是将 CSS 拆分为单独的文件,并仅加载所需的部分。
html<!-- Loading and parsing styles.css is render-blocking --> <link rel="stylesheet" href="styles.css" /> <!-- Loading and parsing print.css is not render-blocking --> <link rel="stylesheet" href="print.css" media="print" /> <!-- Loading and parsing mobile.css is not render-blocking on large screens --> <link rel="stylesheet" href="mobile.css" media="screen and (width <= 480px)" />
上面的示例提供了三组样式——总是会加载的默认样式、只有在打印文档时才会加载的样式,以及只有窄屏设备才会加载的样式。默认情况下,浏览器假定每个指定的样式表都会阻塞渲染。您可以通过添加包含媒体查询的
media
属性来告诉浏览器何时应该应用样式表。当浏览器看到它只需要在特定场景中应用的样式表时,它仍然会下载该样式表,但不会阻塞渲染。通过将 CSS 拆分成多个文件,主渲染阻塞文件(在本例中为styles.css
)会小得多,从而减少了渲染被阻塞的时间。 -
压缩和精简您的 CSS:精简是指在代码投入生产后,删除文件中所有仅用于提高人类可读性的空白。通过精简 CSS,您可以显着缩短加载时间。精简通常作为构建过程的一部分完成(例如,大多数 JavaScript 框架在您构建准备部署的项目时都会精简代码)。除了精简之外,请确保您的网站托管服务器在提供文件之前对其使用 gzip 等压缩技术。
-
简化选择器:人们经常编写比应用所需样式更复杂的选择器。这不仅增加了文件大小,也增加了这些选择器的解析时间。例如
css/* Very specific selector */ body div#main-content article.post h2.headline { font-size: 24px; } /* You probably only need this */ .headline { font-size: 24px; }
让你的选择器不那么复杂和具体也利于维护。简单的选择器很容易理解它们在做什么,而且如果选择器不那么具体,以后需要时也很容易覆盖样式。
-
不要对超出需要范围的元素应用样式:一个常见的错误是使用通用选择器,或者至少是对超出需要范围的更多元素应用样式。这种样式可能会对性能产生负面影响,尤其是在大型网站上。
css/* Selects every element inside the <body> */ body * { font-size: 14px; display: flex; }
请记住,许多属性(例如
font-size
)会从其父级继承值,因此您不需要在所有地方都应用它们。而像Flexbox这样强大的工具需要谨慎使用。在所有地方使用它们可能会导致各种意想不到的行为。 -
使用 CSS Sprites 减少图片 HTTP 请求:CSS Sprites 是一种技术,它将您想要在网站上使用的多个小图片(例如图标)放入一个图片文件中,然后使用不同的
background-position
值来显示您想要在每个不同位置显示的图片块。这可以显着减少获取图片所需的 HTTP 请求数量。 -
预加载重要资源:您可以使用
rel="preload"
将<link>
元素转换为关键资源的预加载器。这包括 CSS 文件、字体和图片。html<link rel="preload" href="style.css" as="style" /> <link rel="preload" href="ComicSans.woff2" as="font" type="font/woff2" crossorigin /> <link rel="preload" href="bg-image-wide.png" as="image" media="(width > 600px)" />
使用
preload
,浏览器会尽快获取引用的资源,并将它们存储在浏览器缓存中,以便在后续代码中引用它们时能更快地使用。预加载用户在页面早期会遇到的高优先级资源非常有用,这样可以使体验尽可能流畅。请注意,您还可以使用media
属性来创建响应式预加载器。另请参阅 web.dev 上的预加载关键资产以提高加载速度 (2020)。
处理动画
动画可以提高感知性能,使界面感觉更灵敏,并让用户在等待页面加载时(例如加载旋转器)感受到进度。然而,更大的动画和更多的动画自然需要更多的处理能力来处理,这可能会降低性能。
最简单的建议是减少所有不必要的动画。您还可以为用户提供一个控件/网站偏好设置,如果他们使用低功耗设备或电池电量有限的移动设备,则可以关闭动画。您还可以使用 JavaScript 来控制是否首先将动画应用于页面。还有一个名为prefers-reduced-motion
的媒体查询,可以根据用户的操作系统级动画偏好来有选择地提供动画样式。
对于必要的 DOM 动画,建议尽可能使用CSS 动画,而不是 JavaScript 动画(Web Animations API 提供了一种使用 JavaScript 直接与 CSS 动画挂钩的方法)。
选择要动画的属性
接下来,动画性能在很大程度上取决于您正在动画的属性。某些属性在动画时会触发重排(因此也会触发重绘),应避免使用。这些属性包括:
- 更改元素的尺寸,例如
width
、height
、border
和padding
。 - 重新定位元素,例如
margin
、top
、bottom
、left
和right
。 - 更改元素的布局,例如
align-content
、align-items
和flex
。 - 添加改变元素几何形状的视觉效果,例如
box-shadow
。
现代浏览器足够智能,只会重绘文档中更改的区域,而不是整个页面。因此,更大的动画成本更高。
如果可能的话,最好对不会引起重排/重绘的属性进行动画。这包括:
在 GPU 上动画
为了进一步提高性能,您应该考虑将动画工作从主线程转移到设备的 GPU(也称为合成)。这通过选择浏览器会自动发送到 GPU 处理的特定动画类型来完成;这些包括
- 3D 变换动画,例如
transform: translateZ()
和rotate3d()
。 - 某些其他属性动画的元素,例如
position: fixed
。 - 已应用
will-change
的元素(参见下面的部分)。 - 以其自身层渲染的某些元素,包括
<video>
、<canvas>
和<iframe>
。
在 GPU 上进行动画可以提高性能,尤其是在移动设备上。然而,将动画转移到 GPU 并非总是那么简单。请阅读 CSS GPU 动画:正确地做 (smashingmagazine.com, 2016) 以获取非常有用的详细分析。
使用 will-change
优化元素更改
浏览器可能会在元素实际更改之前设置优化。这些优化可以通过在需要之前完成可能开销较大的工作来提高页面响应性。CSS will-change
属性向浏览器提示元素预期会如何更改。
注意:will-change
旨在作为最后手段来处理现有性能问题。它不应用于预测性能问题。
.element {
will-change: opacity, transform;
}
优化渲染阻塞
CSS 可以使用媒体查询将样式限定在特定条件下。媒体查询对于响应式网页设计很重要,有助于我们优化关键渲染路径。浏览器会阻塞渲染,直到解析所有这些样式,但不会阻塞它知道不会使用的样式(例如打印样式表)的渲染。通过根据媒体查询将 CSS 拆分为多个文件,您可以防止在下载未使用的 CSS 时阻塞渲染。要创建非阻塞 CSS 链接,请将不立即使用的样式(例如打印样式)移动到单独的文件中,在 HTML 标记中添加一个 <link>
,并添加一个媒体查询,在这种情况下,它声明这是一个打印样式表。
<!-- Loading and parsing styles.css is render-blocking -->
<link rel="stylesheet" href="styles.css" />
<!-- Loading and parsing print.css is not render-blocking -->
<link rel="stylesheet" href="print.css" media="print" />
<!-- Loading and parsing mobile.css is not render-blocking on large screens -->
<link rel="stylesheet" href="mobile.css" media="screen and (width <= 480px)" />
默认情况下,浏览器假定每个指定的样式表都会阻塞渲染。通过添加带有媒体查询的media
属性,告诉浏览器何时应该应用样式表。当浏览器看到它知道只需在特定场景中应用的样式表时,它仍然会下载该样式表,但不会阻塞渲染。通过将 CSS 拆分为多个文件,主渲染阻塞文件(在本例中为styles.css
)会小得多,从而减少了渲染被阻塞的时间。
提升字体性能
本节包含一些提高网页字体性能的有用提示。
总的来说,请仔细考虑您网站上使用的字体。有些字体文件可能非常大(几兆字节)。虽然使用大量字体来增加视觉吸引力可能很诱人,但这会显着减慢页面加载速度,并导致您的网站看起来一团糟。您可能只需要大约两到三种字体,如果您选择使用网页安全字体,您甚至可以减少更多。
字体加载
请记住,字体只在实际使用 font-family
属性应用于元素时才加载,而不是在首次使用 @font-face
at-rule 引用时加载。
/* Font not loaded here */
@font-face {
font-family: "Open Sans";
src: url("OpenSans-Regular-webfont.woff2") format("woff2");
}
h1,
h2,
h3 {
/* It is actually loaded here */
font-family: "Open Sans", sans-serif;
}
因此,使用 rel="preload"
尽早加载重要字体可能是有益的,这样它们在实际需要时会更快可用。
<link
rel="preload"
href="OpenSans-Regular-webfont.woff2"
as="font"
type="font/woff2"
crossorigin />
如果你的 font-family
声明隐藏在一个大型外部样式表中,并且在解析过程的后期才能被访问,那么这更有可能带来好处。然而,这是一个权衡——字体文件相当大,如果你预加载太多,可能会延迟其他资源。
您还可以考虑:
- 使用
rel="preconnect"
与字体提供商建立早期连接。有关详细信息,请参阅 预连接到关键的第三方来源。 - 使用CSS Font Loading API通过JavaScript自定义字体加载行为。
只加载您需要的字形
在为主体文本选择字体时,很难确定其中将使用的字形,尤其是当您处理用户生成内容和/或多种语言的内容时。
然而,如果您知道将使用一组特定的字形(例如,仅用于标题或特定标点符号的字形),您可以限制浏览器必须下载的字形数量。这可以通过创建仅包含所需子集的字体文件来完成。这个过程称为子集化。unicode-range
@font-face
描述符可以用于指定何时使用您的子集字体。如果页面不使用此范围内的任何字符,则不下载该字体。
@font-face {
font-family: "Open Sans";
src: url("OpenSans-Regular-webfont.woff2") format("woff2");
unicode-range: U+0025-00FF;
}
使用 font-display
描述符定义字体显示行为
应用于 @font-face
规则的 font-display
描述符定义了浏览器如何加载和显示字体文件,允许在字体加载或加载失败时,使用备用字体显示文本。这通过使文本可见而不是显示空白屏幕来提高性能,但代价是会出现未样式化文本的闪烁。
@font-face {
font-family: "someFont";
src: url("/path/to/fonts/someFont.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: fallback;
}
使用 CSS 包含优化样式重新计算
通过使用CSS 包含模块中定义的属性,您可以指示浏览器隔离页面的不同部分并独立优化它们的渲染。这可以提高各个部分的渲染性能。例如,您可以指定浏览器在某些容器在视口中可见之前不渲染它们。
contain
属性允许作者精确指定他们希望应用于页面上各个容器的包含类型。这允许浏览器对 DOM 的有限部分重新计算布局、样式、绘制、大小或它们的任意组合。
article {
contain: content;
}
content-visibility
属性是一个有用的快捷方式,它允许作者在容器集合上应用一组强大的包含限制,并指定浏览器在需要时才对这些容器进行布局和渲染。
第二个属性 contain-intrinsic-size
也可用,它允许您为处于包含效果下的容器提供一个占位符大小。这意味着即使容器的内容尚未渲染,它们也会占用空间,从而允许包含在不冒滚动条移动和元素渲染并进入视图时卡顿的风险下发挥其性能魔力。这提高了内容加载时用户体验的质量。
article {
content-visibility: auto;
contain-intrinsic-size: 1000px;
}
优化 :has()
选择器
:has()
伪类提供了强大的选择功能,但需要谨慎使用以避免性能瓶颈。有关编写高效 :has()
选择器的详细指南,请参阅 :has()
参考文档中的性能注意事项。
另见
- CSS 动画性能
- web.dev 上的字体最佳实践 (2022)
- web.dev 上的content-visibility:提升渲染性能的新 CSS 属性 (2022)