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 (max-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 雪碧减少图像 HTTP 请求CSS 雪碧是一种技术,它将您想在网站上使用的几个小图像(例如图标)放到一个图像文件中,然后使用不同的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="(min-width: 601px)" />
    
    使用preload,浏览器会尽快获取引用的资源并将其放入浏览器缓存中,以便在后续代码中引用它们时可以更快地使用它们。预加载用户在页面早期遇到的高优先级资源很有用,这样可以使体验尽可能流畅。请注意,您还可以使用media属性创建响应式预加载器。另请参阅 web.dev 上的预加载关键资源以提高加载速度(2020)

处理动画

动画可以提高感知性能,使界面感觉更灵敏,并使用户在等待页面加载时感觉正在取得进展(例如加载微调器)。但是,较大的动画和较多的动画自然需要更多的处理能力来处理,这会降低性能。

最简单的建议是减少所有不必要的动画。您还可以为用户提供一个控件/网站首选项来关闭动画,如果他们使用的是低功耗设备或电池电量有限的移动设备。您还可以使用 JavaScript 控制是否首先将动画应用于页面。还有一个名为prefers-reduced-motion的媒体查询,可用于根据用户操作系统级别的动画首选项有选择地提供动画样式或不提供动画样式。

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

选择要动画化的属性

接下来,动画性能很大程度上取决于您正在动画化的属性。某些属性在进行动画处理时会触发重排(因此也会触发重绘),应避免使用。这些属性包括

现代浏览器足够智能,可以仅重新绘制文档的更改区域,而不是整个页面。因此,较大的动画成本更高。

如果可能,最好动画化不会导致重排/重绘的属性。这包括

在 GPU 上进行动画处理

为了进一步提高性能,您应该考虑将动画工作从主线程转移到设备的 GPU(也称为合成)。这是通过选择浏览器会自动发送到 GPU 处理的特定类型的动画来完成的;这些包括

在 GPU 上进行动画处理可以提高性能,尤其是在移动设备上。但是,将动画转移到 GPU 上并非总是那么简单。阅读CSS GPU 动画:正确的方法(smashingmagazine.com,2016)以获取非常有用且详细的分析。

使用 will-change 优化元素更改

浏览器可能会在元素实际更改之前设置优化。这些类型的优化可以通过在需要之前执行可能代价高昂的工作来提高页面的响应速度。CSS 的will-change 属性提示浏览器元素预计将如何更改。

注意:will-change 旨在作为最后的手段来尝试解决现有的性能问题。它不应用于预测性能问题。

css
.element {
  will-change: opacity, transform;
}

优化渲染阻塞

CSS 可以使用媒体查询将样式限定在特定条件下。媒体查询对于响应式网页设计非常重要,并且有助于我们优化关键渲染路径。浏览器会阻止渲染,直到它解析所有这些样式,但不会阻止它知道不会使用的样式(例如打印样式表)的渲染。通过根据媒体查询将 CSS 分割成多个文件,您可以防止在下载未使用的 CSS 期间阻止渲染。要创建非阻塞 CSS 链接,请将不立即使用的样式(例如打印样式)移动到单独的文件中,向 HTML 标记中添加<link>,并添加媒体查询,在本例中表示它是打印样式表。

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 (max-width: 480px)" />

默认情况下,浏览器假设每个指定的样式表都是渲染阻塞的。通过使用媒体查询添加 media 属性来告诉浏览器何时应用样式表。当浏览器看到一个样式表,它知道只需要在特定情况下应用它时,它仍然会下载样式表,但不会阻止渲染。通过将 CSS 分割成多个文件,主渲染阻塞文件(在本例中为 styles.css)会小得多,从而减少渲染被阻塞的时间。

提升字体性能

本节包含一些改进网络字体性能的有用技巧。

总的来说,请仔细考虑您在网站上使用的字体。某些字体文件可能非常大(几兆字节)。虽然使用大量字体来增强视觉效果可能很诱人,但这可能会显着减慢页面加载速度,并导致您的网站看起来很乱。您可能只需要大约两到三种字体,如果您选择使用网络安全字体,则可以使用更少的字体。

字体加载

请记住,仅当使用font-family 属性实际将字体应用于元素时才会加载字体,而不是在首次使用@font-face at-规则引用它时。

css
/* 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";
}

因此,使用 rel="preload" 早期加载重要的字体可能会有益,这样当它们实际需要时就可以更快地使用。

html
<link
  rel="preload"
  href="OpenSans-Regular-webfont.woff2"
  as="font"
  type="font/woff2"
  crossorigin />

如果您的 font-family 声明隐藏在一个大型外部样式表中,并且在解析过程的后期才会被访问,则这更有可能带来好处。然而,这是一个权衡——字体文件相当大,如果您预加载了太多字体,可能会延迟其他资源。

您还可以考虑

仅加载您需要的字形

在为正文副本选择字体时,很难确定将使用哪些字形,尤其是在处理用户生成的内容和/或跨多种语言的内容时。

但是,如果您知道要使用一组特定的字形(例如,仅用于标题或特定标点符号的字形),则可以限制浏览器必须下载的字形数量。这可以通过创建一个仅包含所需子集的字体文件来完成。一个称为子集的过程。unicode-range @font-face 描述符可用于指定何时使用子集字体。如果页面不使用此范围内的任何字符,则不会下载字体。

css
@font-face {
  font-family: "Open Sans";
  src: url("OpenSans-Regular-webfont.woff2") format("woff2");
  unicode-range: U+0025-00FF;
}

使用 font-display 描述符定义字体显示行为

应用于 @font-face at-规则,font-display 描述符定义浏览器如何加载和显示字体文件,允许文本在字体加载或加载失败时显示回退字体。这通过使文本可见(而不是出现空白屏幕)来提高性能,但代价是可能会出现未设置样式文本的闪烁。

css
@font-face {
  font-family: someFont;
  src: url(/path/to/fonts/someFont.woff) format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: fallback;
}

使用 CSS containment 优化样式重新计算

通过使用CSS 包含 模块中定义的属性,您可以指示浏览器隔离页面的不同部分并独立优化它们的渲染。这可以提高渲染各个部分的性能。例如,您可以指定浏览器在容器在视口中可见之前不渲染某些容器。

contain 属性允许作者指定他们希望应用于页面上各个容器的包含类型。这允许浏览器重新计算 DOM 的有限部分的布局、样式、绘制、大小或它们的任何组合。

css
article {
  contain: content;
}

content-visibility 属性是一个有用的快捷方式,它允许作者在一组容器上应用一组强大的包含,并指定浏览器在需要之前不应布局和渲染这些容器。

还有一个名为contain-intrinsic-size的第二个属性,它允许您在容器受到包含影响时为其提供一个占位符大小。这意味着即使容器的内容尚未渲染,容器也会占用空间,从而允许包含发挥其性能优势,而不会出现元素渲染并进入视野时滚动条偏移和卡顿的风险。这提高了用户体验的质量,因为内容已加载。

css
article {
  content-visibility: auto;
  contain-intrinsic-size: 1000px;
}

另请参阅