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

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

样式计算

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

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

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

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 开发者工具中的性能面板。要开始使用,请执行以下操作:

  1. 打开开发者工具。
  2. 转到效果标签页。
  3. 点击录制
  4. 与网页互动。

停止录制后,您会看到如下图所示的内容:

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

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

放大 Chrome 开发者工具中已填充性能面板的活动摘要中的问题区域。
开发者工具活动摘要中的长时间运行的帧。

值得仔细研究一下互动(例如滚动)期间长时间运行的帧。如果您看到一个较大的紫色块,请放大活动并选择任何标记为重新计算样式的工作,以详细了解可能成本高昂的样式重新计算工作。

获取长时间运行的样式计算的详细信息,包括受样式重新计算工作影响的元素数量等重要信息。
开发者工具摘要中长时间运行的样式重新计算用时超过 25&nbsp 毫秒。

点击事件会显示其调用堆栈。如果渲染工作是由用户互动导致的,它会调用触发样式更改的 JavaScript。该图表还会显示受到更改影响的元素数量(在本例中为 900 多个元素)以及样式计算所用的时间。您可以使用这些信息开始尝试在代码中查找修复程序。

使用块、元素、修饰符

BEM(块、元素、修饰符)等编码方法纳入了选择器匹配的性能优势。BEM 建议所有代码都有一个类,并且在需要层次结构时,该层次结构也已纳入到类名称中:

.list {
  /* Styles */
}

.list__list-item {
  /* Styles */
}

如果需要修饰符(如最后一个子项示例所示),可以按如下所示添加该修饰符:

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

BEM 是一个很好的着手点,可帮助您从结构角度来组织 CSS,同时因为它促进了样式查询的简化。

如果您不喜欢 BEM,还有其他方法来处理 CSS,但您应该在开始之前评估其性能和工效学设计。

资源

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