网页的生成过程是怎样的?
在这个过程中:
HTML 被 HTML 解析器解析成 DOM 树
CSS 则被 CSS 解析器解析成 CSSOM 树
结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree)
生成布局(flow),即将所有渲染树的所有节点进行平面合成
将布局绘制(paint)在屏幕上
其中,第 4 步和第 5 步是最耗时的部分,这两步合起来就是我们通常所说的渲染。在网页生成的过程中,至少会渲染一次;并且,在用户操作界面的过程中,还会不断地重新渲染。也就是会不断地发生重排和重绘。
什么是重排?
当渲染树 Render tree 中的一部分(或全部)因为 DOM 的变化影响了元素的几何信息(DOM 对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,并将其安放在界面中的正确位置,这个过程就称为重排(reflow),重排也叫作回流。
什么是重绘?
当渲染树 Render tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,而没有影响到布局,例如改变 visibility、outline、背景色等属性,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观,这个过程就称为重绘(repaint)。
重排与重绘的关系是怎样的?
重绘不会引起重排,但重排一定会引起重绘。一个元素的重排通常会带来一系列的反应,甚至触发整个文档的重排和重绘,性能代价是高昂的。
什么情况会触发重排和重绘?
1、触发重排的条件
页面渲染初始化时;(这个无法避免)
浏览器窗口改变尺寸;
元素尺寸改变时;
元素位置改变时;
元素内容改变时;
添加或删除可见的 DOM 元素时。
2、触发重绘的条件
常见的引起重绘的属性:
color
border-style
visibility
background
text-decoration
background-image
background-position
background-repeat
outline-color
outline
outline-style
border-radius
outline-width
box-shadow
background-size
display: none 和 visibility: hidden 有什么区别?
- 通过 display: none 隐藏一个 DOM 节点,会触发重排和重绘;
- 通过 visibility: hidden 隐藏一个 DOM 节点,只触发重绘,因为没有几何变化。
浏览器的渲染队列是什么意思?
当我们修改了元素的几何属性,导致浏览器触发重排或重绘时,它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。
以下代码将会触发几次渲染?
1 | div.style.left = '10px'; |
根据前面的描述,这段代码理论上会触发4次重排+重绘,因为每一次都改变了元素的几何属性。但实际上最后只触发了一次重排,这都得益于浏览器的渲染队列机制。
但是如果我们像下面这样:
1 | div.style.left = '10px'; |
这段代码就会触发4次重排+重绘,因为在 console 中请求了这几个样式信息,无论何时浏览器都会立即执行渲染队列的任务(强制刷新队列),即使该值与操作中修改的值没关联。
强制刷新队列的 style 样式请求:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()、 或者 IE 的 currentStyle
我们在开发中应当避免这些能够强制刷新队列的操作。
如何优化重排?
1、读写分离
还是拿上面的强制刷新队列的例子,如果我们改成这样:
1 | div.style.left = '10px'; |
这次只触发了一次重排,因为:在第一个 console 的时候,浏览器把之前上面四个写操作的渲染队列都给清空了;剩下的 console,因为渲染队列本来就是空的,所以并没有触发重排,仅仅拿值而已。
2、样式集中改变
将多次改变样式属性的操作合并成一次操作,减少 DOM 访问
1 | div.style.left = '10px'; |
优化:
1 | el.className += " className"; // 直接改变 class |
3、离线操作
如果要批量添加 DOM,可以让要操作的元素进行「离线处理」,处理完后一起更新。离线处理的意思是:
隐藏要操作的 DOM
在要操作 DOM 之前,通过 display 属性隐藏 DOM,当操作完成之后,再将元素的 display 属性为可见,因为不可见的元素不会触发重排和重绘。
通过使用 DocumentFragment 创建一个DOM 碎片,在它上面批量操作 DOM,操作完成之后,再添加到文档中,这样只会触发一次重排。
4、position 属性的应用
将需要多次重排的元素,position 属性设为 absolute 或 fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
5、在内存中构建
在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的 html 片段,再一次性添加到文档中去,而不是直接操作 DOM,循环添加每一行。