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 开发者工具中的性能面板。要开始使用,请执行以下操作:
- 打开开发者工具。
- 转到效果标签页。
- 点击录制。
- 与网页互动。
停止录制后,您会看到如下图所示的内容:
顶部的条是一个微型火焰图,也绘制了每秒帧数。activity 越靠近界面栏底部,浏览器绘制的帧的速度就越快。如果您看到火焰图顶部平缓且上方有红条,则表明您的某些工作导致了长时间运行的帧。
值得仔细研究一下互动(例如滚动)期间长时间运行的帧。如果您看到一个较大的紫色块,请放大活动并选择任何标记为重新计算样式的工作,以详细了解可能成本高昂的样式重新计算工作。
点击事件会显示其调用堆栈。如果渲染工作是由用户互动导致的,它会调用触发样式更改的 JavaScript。该图表还会显示受到更改影响的元素数量(在本例中为 900 多个元素)以及样式计算所用的时间。您可以使用这些信息开始尝试在代码中查找修复程序。
使用块、元素、修饰符
BEM(块、元素、修饰符)等编码方法纳入了选择器匹配的性能优势。BEM 建议所有代码都有一个类,并且在需要层次结构时,该层次结构也已纳入到类名称中:
.list {
/* Styles */
}
.list__list-item {
/* Styles */
}
如果需要修饰符(如最后一个子项示例所示),可以按如下所示添加该修饰符:
.list__list-item--last-child {
/* Styles */
}
BEM 是一个很好的着手点,可帮助您从结构角度来组织 CSS,同时因为它促进了样式查询的简化。
如果您不喜欢 BEM,还有其他方法来处理 CSS,但您应该在开始之前评估其性能和工效学设计。
资源
主打图片来自 Unsplash 用户,由 Markus Spiske 制作。