浏览器的回流与重绘

284 阅读5分钟

浏览器的渲染过程

A458FFDB-6818-4377-B0CF-C94C88D38AC7.png

  1. 解析HTML文件,生成DOM树,解析css文件,生成CSSOM树
  2. 将DOM树和CSSOM树合并,生成Render Tree(渲染树)
  3. Layout(回流):根据生成的渲染树,确定节点的几何信息(尺寸和位置)
  4. Painting(重绘):根据渲染树已经回流得到的几何信息,确定节点的绝对像素(包括颜色等等)
  5. Display:将像素发送给CPU,展示在页面。

CSSOM(CSS Object Modal):CSS对象模型,是树形式的所有CSS选择器及每个选择器相关属性的映射。它允许用户动态读取和修改 CSS样式

生成渲染树

注意:渲染树只包含可见节点,如 display: none ,这种节点,不会出现在渲染树

46CBEB65-8D9F-48DC-AE37-BAA53866038A.png

回流/重排(Reflow)

当DOM的变化,引发了元素的几何信息(如位置,尺寸)变更,浏览器需要重新计算元素的几何信息,此过程称为重排。

引发重排的场景

  1. 页面初始化渲染,必然引发重排,且是消耗最大的
  2. 删除/添加DOM元素
  3. 元素尺寸发生改变
  4. 元素位置发生改变
  5. 元素内部文字,字体大小发生改变
  6. 元素内容发生改变,如文字增加,或变成不同尺寸的图片
  7. 浏览器视口发生改变
  8. 查询某些属性或者调用某些方法,如offsetWidth,getComputedStyle等,会触发,因为需要“即时性”。

重排的影响范围

浏览器渲染界面是基于流式布局的,所以触发重排的时候,会影响到其周围的元素。影响的范围有两种

全局范围:从根节点开始,对整个渲染树进行重新布局 局部:对渲染树的某部分或某个渲染对象进行重新布局

局部范围重排: 如果,将某个DOM的几何信息,完全固定,然后在dom的内部,进行重排。不会影响外部元素。

重绘(Painting)

当一个元素的外观发生改变,但没有改变布局时,重新将元素外观绘制出来,此过程称为重绘。

重排与重绘

重绘:某些元素的外观发生改变

重排:重新生成布局,重新排列元素。

如上,仅改变元素的外观时,一般不会引起元素布局的变更,即不会导致重排。但,如果浏览器完成重排,需要重新绘制此次重排受影响的部分。 故,重排必导致重绘,重绘不一定导致重排

重排优化

可知,重排是非常消耗性能的,且会严重影响到用户体验。故,我们需要尽量减少重排

浏览器的优化机制

大多数浏览器,会通过队列化修改并批量执行,以此来优化,减少重排次数。 浏览器会将修改操作放在队列之中,达到阈值之后,进行重排,清空队列。 但是,获取布局信息的操作,会强制队列刷新

offsetTopoffsetLeftoffsetHeightoffsetWidth
scrollTopscrollLeftscrollWidthscrollHeight
clientTopclientLeftclientWidthclientHeight
getcomputedStyle()getBoundingClientRect()

具体可参考gist.github.com/paulirish/5…

减少重绘和重排

1、减少重绘重排次数,样式集中改变

const el = document.getElementById('test') el.style.padding = '5px' el.style.borderLeft= '1px' el.style.borderRight = '2px' 例中,有三个样式属性被改变,每一个都会影响元素的几何属性,引起重排。 以上,浏览器对其做了优化,只会触发重排。 如果是在旧版的浏览器,会导致三次重排。

所以,我们可以采取以下的方式: 使用cssText

const el = document.getElementById('test')
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;'

使用className

const el = document.getElementById('test')
el.className += 'active'
2、分离读写操作

当我们访问元素的一些属性时,会导致浏览器强制清空队列,进行强制同步布局。 举个例子

const el = document.getelementById('test')
el.style.left = el.offsetLeft + 1 + 'px'
el.style.top = el.offsetTop + 1 + 'px'

如上,同步地读、写元素的几何信息,会导致浏览器重复重排

const el = document.getElementById('text')
const left =  el.offsetLeft
const top = el.offsetTop

el.style.left = left.+ 1 + 'px'
el.style.top = top + 1 + 'px'

我们修改为如上的先读后写的方式,只会触发一次重排

3、批量修改DOM时

如果,我们要对某一个节点进行反复修改,我们可以先让该节点脱离文档流,执行修改之后,再插入文档流

  1. 使用display: none
  2. 使用documentFragment创建一个dom碎片,在其上批量操作dom
  3. 复制节点,在副本上操作,然后替换原节点 注:因为浏览器的队列合并处理,以上方法在现代浏览器中,收效甚微
4、绝对定位脱离文档流

如果有某个元素,其几何信息会频繁地发生变更,比如动画。 可以让它脱离文档流,绝对定位。 这样,它的重排仅出现在局部,不会导致父元素及邻近元素的频繁重排

5、css3硬件加速(GPU加速)

使用css3硬件加速,可以让 transform、opacity、filters这些动画不引起回流重绘。但是对于动画的其他属性,如background-color,会引起回流重绘,不过它同时还可以提升这些动画的性能。

总结

渲染的三个阶段Layout,Paint,Composite Layers。Layout:重排,又叫回流。Paint:重绘,重排重绘都是在CPU中发生的。 Composite Layers:CPU把生成的BitMap(位图)传输到GPU,渲染到屏幕。

参考链接: juejin.cn/post/684490… github.com/chenjigeng/…