FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

[译]渲染性能优化之Style篇 #197

Open FrankKai opened 4 years ago

FrankKai commented 4 years ago

原文链接:https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations

computed style计算

改变DOM有这么几种方式,可以通过增加删除节点,改变attributes,classes,或者animation,这些操作都可能导致浏览器重新计算styles,然后再很多情况下会layout(reflow)整个页面,或者是页面的一部分。这个过程叫做computed style calculation

computed style calculation分为以下2步: 1.找到元素 find element 创建一个匹配到的选择器集合,本质上是浏览器找出元素应用了哪些class,伪类选择器和id。 2.计算最终样式 calculate final(computed) style 从匹配到的选择器拿到全部的style rules,然后找出这个元素的final style是什么。在Blink (Chrome和Opera的渲染引擎)中,至少在今天,这些过程的消耗大致相当:

大约50%的时间用于计算元素的computed style,用于匹配选择器。另一半时间用于根据匹配的规则构造RenderStyle(computed style)。

如何降低computed style计算的消耗

如何尽可能减少reflow(layout)导致的computed style calculation消耗呢?

减少选择器的复杂性

用一句话概括就是:通过一个特殊命名的class去替代复杂的选择器,降低浏览器在 style 阶段的性能损耗。 具体原因可以在下文的分析中找到。

最简单的通过CSS去引用一个元素的方式只需要一个class:

.title {
/* styles */
}

但是,随着项目的成长,会出现更加复杂的CSS,例如我们可能用下面这种选择器:

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

为了知道需要应用到浏览器的样式,必须这样问:“这是一个class名为title的元素,然后它的父元素需要找到第-n+1个class为box的元素?” 想把这个搞清楚的话,需要花费大量的时间,一般需要取决于使用的选择器和提问的浏览器。 上面这种消耗性能且可读性差的选择器可以改为下面这样:

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

我们需要花费一些时间去想class的名字,但是这对于浏览器来说会变得特别简单。 在之前的版本中,为了拿到最后一个元素,浏览器必须首先知道所有其他的元素所有信息,然后去判断后面的元素是不是最后一个子元素。这种方式显然比直接简单地通过class做匹配更加昂贵。

减少computed style calculation涉及到的元素个数

另一个性能优化需要考虑的点,对于大量style更新的情况是非常重要的因素,是元素更改时需要执行的总工作量。

大多数情况下,计算computed style的消耗最高的情况是都需要元素的数量乘以选择器的数量,因为每个元素需要对每个style做一遍检查,以便查看是否匹配。

注意:在过去,如果在body元素上更改一个class,那么页面中的所有子元素都需要重新计算它们的计算样式。幸运的是,现在情况已经不同了;相反,有些浏览器维护每个元素特有的一小部分规则集合,如果更改这些规则,就会导致重新计算元素的样式。这意味着一个元素可能需要也可能不需要重新计算,这取决于它在树中的位置,以及具体发生了什么变化。

样式的计算可以直接指定一些元素而不是将页面作为全部。在现代浏览器中这不再是什么问题了,因为发生变化后浏览器不必检查所有的元素。老式的浏览器,对这类task的优化并不高。也就是说我们需要减少无效元素的数量。

注意:注意:如果您对Web组件感兴趣,那么值得注意的是,这里的样式计算有一点不同,因为默认情况下,样式不会跨越shadow DOM边界,并且作用域仅限于单个组件,而不是整个树。但是,总的来说,同样的概念仍然适用:具有简单规则的较小的树比大型树或复杂规则处理得更有效。

衡量你的样式重计算的损耗

最容易和最简单的度量你的Style Recalculation Cost是使用Chrome 的DevTools。 可以使用Performance Tab,开始记录并且交互。当停止记录时,你会看到类似下面这样的一张图。 image

顶部的数值表示每秒的帧数,如果你看下面的这张图,超过60fps的有一个柱子,它是一个long running frame。 image

如果有long running frame,一般是由scrolling,一些其他的交互,然后它需要进一步的审查。

如果你有一个大的紫色的block,就像上面的,可以点击查看详情。 image

可以看到一次Recalculate Style event花费了18ms,并且发生才scroll期间,导致了抖动。 点击事件可以看到call stack和影响到的元素数量,以及导致Style Recalculation的原因。

手上项目的Recalculate style消耗

image

使用Block,Element,Modifier

BEM是一个很好的性能优化的方案,因为它建议每一个元素都有一个单独的class,并且还会使用继承,最后的结果类似下面这样:

.list {  }
.list__list-item{  }

如果你想对最后一个元素做一些什么操作,最好是按照下面这样的方式去做:

.list__list-item--last-child {  }

如果你在找一个更好的组织CSS的方式,BEM是一个很好的方式,因为它结构上可读性强,而且简化浏览器的style这一步。

如果你不喜欢BEM,可以自己组织CSS,但是一定要注意性能。