2

刚入行前端的时候是不是经常看到有文章说尽量不要用CSS通配符*,CSS选择器层叠最好不要超过三层,HTML少使用table,结构也要尽量简单一些...这一切说的不无道理,过多的使用确实会造成浏览器渲染的性能降低,当你认识了reflow和repaint之后,你会发现这些还真不能用太多。

一、浏览器渲染过程

不同的浏览器渲染过程实际上并不相同(由渲染引擎决定),但是依旧存在一致的部分,大致过程如下图:

图片描述

  1. 解析HTML以构建DOM树:渲染引擎解析HTML文档,转换树中的HTML标签或JS生成的标签生成DOM节点
  2. 解析CSS以构建样式结构体:渲染引擎解析CSS(包括外部CSS文件、样式元素以及JS生成的样式)成样式结构体,根据CSS选择器计算出节点的样式
  3. 构建渲染树:从根节点递归调用,计算每一个元素的大小、位置以及每个节点所应该出现在屏幕上的精确坐标
  4. 绘制渲染树:渲染引擎遍历渲染树将其绘制出来

二、什么是 reflow & repaint

其实在上面浏览器渲染过程中的第三步和第四步分别就是回流(reflow)和重绘(repaint)。当第一次打开一个页面时,至少会有一次回流和重绘。之后,如果渲染树发生了变动,那么可能会触发回流或重绘中的一个或二者。

回流:如果渲染树的节点发生了结构性变化,例如宽高、位置、隐藏上有变化时,那么就会触发一次回流
重绘:如果渲染树的节点发生了非结构性变化,例如背景色、颜色、字体上有变化时,那么就会触发一次重绘

回流必将引起重绘,而重绘不一定会引起回流。回流的成本比重绘的成本要高得多,因为一个节点的回流很有可能导致子结点,父节点回流。

三、触发 reflow & repaint

  • 页面初始化渲染
  • DOM元素的添加、修改、删除
  • 移动DOM或着DOM发生了动画
  • resize浏览器窗口、滚动页面
  • 修改DOM元素的字体颜色
  • 激活CSS伪类
  • 某个样式的添加、修改、删除
  • display: none会触发reflow,visibility: hidden只会触发repaint,因为没有发生位置变化
  • 读取元素的某些属性(没想到吧...)

现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。但是当获取某些属性时,浏览器为了获得正确的值也会提前触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()或者IE的currentStyle

四、减少 reflow & repaint

回流和重绘是不可避免的,我们只能说将它们对性能的影响减到最小,既然我们知道什么情况会触发它们,那就从这些方面入手:

  1. 让需要改变的元素进行“离线处理”,处理完后一起更新

    • 使用DocumentFragment进行缓存操作,触发一次回流和重绘
    • 使用display: none,触发两次回流和重绘(由于display: none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的回流,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次回流)
  2. 将需要多次回流的元素的position属性设为absolute或fixed(设为float没有完全脱离文档流,这个很微妙),这样元素就脱离了文档流,它的变化不会影响到其他元素的布局,不会导致一个完整回流
  3. 不要把DOM节点的属性值放在一个循环里作为循环的变量,这会导致大量地读写这个节点的属性
  4. 不要一条一条地修改样式,将多次改变样式属性的操作合并成一次(一般人也不会这样做)
  5. 不要用table布局,table中某个元素一旦触发回流就会导致table里所有的其它元素回流。在适合用table的场合,可以设置table-layout为auto或fixed,这样可以让table一行一行的渲染,这种做法也是为了限制回流的影响范围(一般我们可以通过ul li的布局替代之)
  6. 避免使用CSS的JavaScript表达式(这种规则已过时)

总之,在以后的开发中我们要尽量避免大量、频繁的操作DOM元素,因为DOM操作的代价实在是太昂贵了(这也是Virtual DOM应运而生的原因)。在书写HTML时要避免不必要的层级,书写CSS时避免嵌套过深、规则过于复杂,尤其是后代选择器,匹配选择器也会耗费更多的CPU。


Mr_Nice
94 声望3 粉丝

致力于前端开发、