缩小样式计算的范围并降低其复杂性

视觉变化通常是触发 JavaScript 的。有时是直接通过样式操作,有时是会产生视觉变化的计算,例如搜索某些数据或对其进行排序。时机不当或长时间运行的 JavaScript 可能是导致性能问题的常见原因,您应该设法尽可能减少其影响。

通过添加和移除元素、更改属性、类或通过动画更改 DOM 都会导致浏览器重新计算元素样式,在许多情况下,还会对页面或页面的某些部分进行布局(或自动重排)。此过程称为“计算样式的计算”

计算样式的第一部分是创建一组匹配的选择器,这实际上是浏览器弄清楚哪些类、伪选择器和 ID 适用于任何给定元素。

该过程的第二部分涉及从匹配选择器中获取所有样式规则,并计算出元素的最终样式。

摘要

  • 如何减少样式计算费用可以减少互动延迟时间。
  • 降低选择器的复杂性;使用以类为中心的方法(例如 BEM)。
  • 减少必须计算样式的元素数量。

样式重新计算时间和互动延迟时间

Interaction to Next Paint (INP) 是一项以用户为中心的运行时性能指标,用于评估网页对用户输入的整体响应情况。通过此指标评估互动延迟时间时,该指标衡量从用户与网页互动开始,直到浏览器绘制下一帧(显示对界面进行相应视觉更新)为止的时间。

绘制下一帧所需的时间是影响互动的一个重要部分。为呈现下一帧而完成的渲染工作由许多部分组成,包括计算布局、绘制和合成工作之前发生的页面样式。虽然本文只关注样式计算开销,但必须强调,减少互动固有的渲染阶段任何部分都会减少总延迟时间(包括样式计算)。

降低选择器的复杂性

在最简单的情况下,您可以在 CSS 中引用只有一个类的元素:

.title {
  /* styles */
}

但是,随着任何项目的扩展,都可能会导致 CSS 变得越来越复杂,因此最终的选择器可能如下所示:

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

为了了解这些样式如何应用于页面,浏览器必须有效地询问“这个元素是否具有类 title,其父元素恰好是负第 n 个子元素加上 1 个元素,其类为 box?”。弄清楚此问题可能需要花费很多时间,具体取决于使用的选择器以及相关浏览器。选择器的预期行为可以更改为类:

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

您可能会觉得类的名称有问题,但对于浏览器来说,这项工作就简单得多了。例如,在前一个版本中,要想了解该元素是否为其类型的最后一个元素,浏览器必须先了解所有其他元素的相关信息,以及其后面的是否有任何元素(该元素是否为 nth-last-child),这可能比简单地将选择器与元素匹配(因为其类匹配)开销更大。

减少要设置样式的元素数量

另一个性能考虑因素(通常是许多样式更新的最重要因素)是元素发生更改时需要完成的工作量。

一般来说,计算元素的计算样式最糟糕的开销是元素数量乘以选择器计数,因为对于每个样式,每个元素都需要至少检查一次,看看它是否匹配。

通常,样式计算可以直接针对几个元素,而不是使整个网页失效。在现代浏览器中,这往往不是什么问题,因为浏览器不必检查可能受更改影响的所有元素。另一方面,旧版浏览器未必针对此类任务进行了优化。您应尽可能减少失效元素的数量

衡量样式重新计算的开销

测量样式重新计算的开销的一种方法是使用 Chrome 开发者工具中的性能面板。首先,打开开发者工具,前往标记为 Performance(性能)的标签页,点击“record”(记录),然后与页面交互。停止录制后,您会看到如下图所示的内容:

显示样式计算的开发者工具。

顶部的条是一个微型火焰图,也绘制了每秒帧数。activity 越靠近标签栏底部,浏览器绘制的帧速度就越快。如果您看到火焰图顶部平缓且上方有红色条带,则表明您的某些工作导致了长时间运行的帧。

正在放大 Chrome 开发者工具中已填充性能面板的活动摘要中的问题区域。

如果您在滚动之类的互动期间有一个长时间运行的帧,则应对其进行进一步审查。如果您有较大的紫色块,请放大活动并选择任何标记为重新计算样式的工作,以详细了解可能比较昂贵的样式重新计算工作。

获取长时间运行的样式计算的详细信息,包括受样式重新计算工作影响的元素数量等重要信息。

在这次抓取中,一项长时间运行的样式重新计算工作需要超过 25 毫秒的时间。

如果您点击事件本身,则会看到一个调用堆栈。如果渲染工作是由用户互动触发的,则会调用 JavaScript 中负责触发样式更改的位置。除此之外,您还可以获取受此更改影响的元素数量(在本例中仅有 900 多个元素)以及完成样式计算所用的时间。您可以使用这些信息开始尝试在代码中查找修复程序。

使用块、元素、修饰符

BEM(块、元素、修饰符)等编码方法实际上纳入了上述选择器匹配性能优势,因为它建议所有元素都有单个类,并且在需要层次结构时,也纳入了类的名称:

.list {
  /* Styles */
}

.list__list-item {
  /* Styles */
}

如果您需要一些修饰符,就像在上面我们希望为最后一个子项执行一些特殊操作一样,可以按如下方式添加:

.list__list-item--last-child {
  /* Styles */
}

如果您正在寻找一种好的方法来组织您的 CSS,BEM 是一个很好的起点,无论是从结构的角度来看,还是由于该方法促进了样式查找的简化。

如果您不喜欢 BEM,还可以通过其他方法构建 CSS,但是在评估性能方面的考虑因素时,应同时考虑该方法的人体工程学。

资源

主打图片来自 Unsplash 用户,由 Markus Spiske 制作。