是什么
在HTML中,每个元素可以理解为一个盒子,在浏览器解析过程中,会涉及到回流和重绘:
- 回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置
- 重绘:当计算好盒模型的位置,大小及其他属性后,浏览器根据每个盒子特性进行绘制
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树Render Tree
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上
当我们对DOM的修改引发了DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来),即回流。
当对DOM的修改导致样式变化(color或background-color)却为影响几何属性,浏览器不需要重新计算元素的几何苏醒,直接为该元素绘制新的样式,这里仅仅触发重绘。
如何触发
要想减少回流和重绘的次数,首先要了解回流和重绘是如何触发的
回流触发时机
回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距,内边框,边框大小,高度,宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染(不可避免)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小)
还有一些容易被忽略的操作:获取一些特定属性的值
offsetTop,offsetLetf,offsetWidth,offsetHeight,scrollTop,scrollLeft,scrollWidth,scrollHeight,clientTop,clientLeft,clientWidth,clientHeight
这些属性有一个共性,就是需要通过即时计算得到,因此浏览器为了获取这些值,也会进行回流,除此之外getComputedStyle方法的原理也一样
重绘触发时机:
触发回流一定会触发重绘
引起重绘·的一些其他行为:
- 颜色修改
- 阴影修改
浏览器优化机制
由于每次重排都会造成额外的计算消耗,因此大多数浏览器会通过队列化修改并批量执行来优化重排过程,浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列
当获取布局信息操作时,会强制队列刷新,包括前面讲到的offsetTop等方法都会返回最新的数据,因此浏览器不得不清空队列,触发回流重绘来返回正确的值
如何减少
- 如果设定元素的样式,通过修改元素的class类型(尽可能在DOM树的最里层)
- 避免设置多项内联样式
- 应用元素的动画,使用position属性为fixed或absolute的元素
- 避免使用table布局,table布局中每个元素的大小以及内容的改动,都会导致整个table的重新计算
- 对于那些复杂动画,对其设置position:fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响
- 使用css3硬件加速,可以让transform,opacity,filters这些动画不会引起回流重绘
- 避免使用CSS的JavaScript表达式
在使用JavaScript动态插入多个节点时,可以使用DocumentFragment,创建最后一次插入,就能避免多次的渲染性能
例如,多次修改一个元素布局时,
1 | const el=document.getElementById("el") |
每次循环都需要获取多次offset
属性,比较糟糕,可以使用变量的形式缓存起来,待计算完毕再提交给浏览器发出重计算请求
1 | const el=document.getElementById("el") |
我们还可避免修改样式,使用类名去合并样式
1 | const container=document.getElementById("container") |
使用类名去合并样式
1 | 1<style> |
前者每次单独操作,都去触发一次渲染树更改(新浏览器不会),
都去触发一次渲染树更改,从而导致相应的回流与重绘过程
合并之后,等于我们将所有的更改一次性发出
我们还可以通过通过设置元素属性display: none
,将其从页面上去掉,然后再进行后续操作,这些后续操作也不会触发回流与重绘,这个过程称为离线操作
1 | const container = document.getElementById('container') |
离线操作后
1 | let container = document.getElementById('container') |