在了解什么是重构和回流之前,我们应该先看看浏览器是怎么渲染的?
浏览器的渲染过程:
1.处理HTML脚本,生成DOM树(DOM树里包含所有的HTML标签,包括display:none和js动态添加的元素等)
2.处理CSS脚本,生成CSSOM树(DOM和CSSOM是独立的数据结构)
3.将DOM树和CSSOM树合并为渲染树,render树中不包含定位和几何信息。虽然,render树与dom树类似但还是有区别的。render树中不包含隐藏的节点 (比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden隐藏的元素还是会包含到 render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。
4.对render树中的内容进行布局,计算每个节点的几何外观
5.将渲染树中的每个节点绘制到屏幕中.
什么是重构和回流
重构
:当元素的某些属性发生变化,这些属性又只影响元素的外观和风格,而不改变元素的布局、大小比如颜色、背景。此时触发的浏览器行为称作重构。回流
:当元素的布局、大小规模和显示方式发生改变时,触发的浏览器行为叫回流。而且,每个页面都会在第一次加载时触发回流。
注意:回流必将引起重绘,而重绘不一定伴随回流。同时,回流对性能的影响要大于重构。
什么操作会引起重绘、回流
其实任何对render tree中元素的操作都会引起回流或者重绘,比如:
- 添加、删除元素(回流+重绘)
- 隐藏元素,display:none(回流+重绘),visibility:hidden(只重绘,不回流)
- 移动元素,比如改变top,left(jquery的animate方法就是,改变top,left不一定会影响回- 流),或者移动元素到另外1个父元素中。(重绘+回流)
- 对style的操作(对不同的属性操作,影响不一样。比如,元素尺寸改变——边距、填充、边框、宽度和高度,内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;)
- 还有一种是用户的操作,比如改变浏览器大小,改变浏览器的字体大小等(回流+重绘)
浏览器对回流的优化
因为,回流花销很大,所以大部分浏览器对于回流都会进行优化,浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。
虽然有了浏览器的优化,但有些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能就起不到作用了。当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列,比如:
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop/Left/Width/Height
- clientTop/Left/Width/Height
- width,height
- 请求了getComputedStyle(), 或者 IE的 currentStyle
当你请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。
如何减少回流、重绘
根据上面触发回流和重绘的操作,我们可以知道只要减少对render tree的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,就能减少回流、重绘了,尽量利用好浏览器的优化策略。
具体做法有:
1.直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器)
2.让要操作的元素进行”离线处理”,处理完后一起更新:
- 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
- 使用display:none技术,只引发两次回流和重绘;
- 使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;
3.不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存
4.让元素脱离动画流,减少回流的Render树的规模(即让动画的元素脱离文档流,使用absolute定位等等)。