缩小样式计算的范围和复杂性

JavaScript 通常是视觉变化的触发器。有时它通过样式操作直接进行这些更改,有时通过导致视觉变化的计算(例如搜索或排序数据)进行更改。定时不当或长时间运行的 JavaScript 可能是性能问题的常见原因,您应该尽可能减少其影响。

样式计算

通过添加和删除元素、更改属性、类或播放动画来更改 DOM 会导致浏览器重新计算元素样式,并且在许多情况下,还会重新计算部分或整个页面的布局。此过程称为样式计算

浏览器首先通过创建一组匹配的选择器来计算样式,以确定哪些类、伪选择器和 ID 应用于任何给定元素。然后,它处理来自匹配选择器的样式规则,并确定元素具有哪些最终样式。

样式重新计算在交互延迟中的作用

交互到下次绘制 (INP) 是一项以用户为中心的运行时性能指标,用于评估页面对用户输入的总体响应能力。它衡量从用户与页面交互到浏览器绘制下一帧以显示用户界面相应视觉更新的交互延迟。

交互的一个重要组成部分是绘制下一帧所需的时间。为呈现下一帧而完成的渲染工作由许多部分组成,包括在布局、绘制和合成工作之前发生的页面样式计算。本指南侧重于样式计算成本,但减少交互的总渲染持续时间的任何部分也会减少其总延迟。

降低选择器的复杂性

简化 CSS 选择器可以帮助加快页面的样式计算。最简单的选择器在 CSS 中仅使用类名引用元素

.title {
  /* styles */
}

但是,随着任何项目的增长,它可能需要更复杂的 CSS,您最终可能会得到如下所示的选择器

.box:nth-last-child(-n+1) .title {
  /* styles */
}

为了确定这些样式如何应用于页面,浏览器必须有效地询问“这是一个类为 title 的元素,其父元素是类为 box 的元素,并且是其父元素的倒数第 n+1 个子元素吗?弄清楚这一点可能需要浏览器花费一些时间。为了简化这一点,您可以将选择器更改为更具体的类名

.final-box-title {
  /* styles */
}

这些替换类名可能看起来很笨拙,但它们使浏览器的工作变得更加简单。例如,在以前的版本中,为了让浏览器知道元素是其类型的最后一个,它必须首先了解所有其他元素的所有信息,以确定在其之后是否有任何元素可能是 nth-last-child。这可能比仅根据元素的类名将选择器与元素匹配在计算上昂贵得多。

减少正在设置样式的元素数量

另一个性能考虑因素——通常比选择器复杂性更重要——是元素更改时需要完成的工作量。

一般来说,计算计算元素样式的最坏情况成本是元素数量乘以选择器计数,因为浏览器需要至少针对每种样式检查每个元素,以查看它是否匹配。

样式计算可以直接针对少量元素,而不是使整个页面失效。在现代浏览器中,这往往不是什么大问题,因为浏览器并不总是需要检查更改可能影响的所有元素。另一方面,旧浏览器并不总是针对此类任务进行优化。在可能的情况下,您应该减少失效元素的数量

测量您的样式重新计算成本

您可以通过几种方法来衡量浏览器中样式重新计算的成本。每种方法都取决于您是想在开发环境中的浏览器中进行测量,还是想衡量此过程在您网站上的真实用户身上花费的时间。

在 Chrome DevTools 中测量样式重新计算成本

衡量样式重新计算成本的一种方法是使用 Chrome DevTools 中的性能面板。执行以下操作即可开始

  1. 打开 DevTools。
  2. 导航到性能标签页。
  3. 选中选择器统计信息复选框(可选)。
  4. 点击记录
  5. 与页面交互。

停止录制后,您将看到类似下图的内容

DevTools showing style calculations.
显示样式计算的 DevTools 报告。

顶部的条带是一个微型火焰图,它还绘制了每秒帧数。活动越靠近条带底部,浏览器绘制帧的速度就越快。如果您看到火焰图在顶部趋于平缓,并且上方有红色条,则说明您的工作导致了长时间运行的帧。

Zooming in on
    a trouble area in Chrome DevTools in the activity summary of the populated
    performance panel in Chrome DevTools.
DevTools 活动摘要中的长时间运行帧。

在滚动等交互期间长时间运行的帧值得仔细查看。如果您看到一个大的紫色块,请放大活动并选择任何标记为重新计算样式的工作,以获取有关可能代价高昂的样式重新计算工作的更多信息。

Getting the
    details of long-running style calculations, including vital information such
    as the amount of elements affected by the style recalculation work.
DevTools 摘要中耗时略超过 25 毫秒的长时间运行的样式重新计算。

点击事件会显示其调用堆栈。如果渲染工作是由用户交互引起的,它会指出触发样式更改的 JavaScript。它还显示了更改影响的元素数量——在本例中略高于 900 个元素——以及样式计算所花费的时间。您可以使用此信息开始尝试在代码中找到修复程序。

如果您在执行跟踪之前在性能面板设置中选中了 选择器统计信息复选框,则跟踪中的底部面板将有一个同名的附加标签页。

The CSS selector stats table as it
    appears in the performance panel of Chrome DevTools. This table contains
    headers and corresponding data for things such as elapsed time, match
    attempts, match count, percentage of non-matching nodes, selectors, and
    the style sheet they can be found in.
选择器统计信息表,如 Chrome DevTools 的性能面板中所示。

此面板提供有关每个选择器的相对成本的有用数据,使您能够识别代价高昂的 CSS 选择器。

有关更多信息,请参阅 CSS 选择器统计信息文档

测量真实用户的样式重新计算成本

如果您想知道真实网站用户发生样式重新计算需要多长时间,Long Animation Frames API 为您提供了执行此操作所需的工具。来自此 API 的数据已添加到 web-vitals JavaScript 库,包括样式重新计算时间。

如果您怀疑交互的呈现延迟是页面 INP 的主要贡献因素,您需要弄清楚有多少时间用于重新计算页面上的样式。有关更多信息,请阅读关于如何测量现场样式重新计算时间

资源