重绘与重排
最近看了一本书《高性能JavaScript》,里面有很多有用的代码片段,后面我准备把一些摘抄记录下来。
今天主要想说说重绘与重排,这个也是《高性能JavaScript》中讲的一部分,我来总结一下吧。
首先先要了解一下浏览器渲染页面的过程,当浏览器下载完页面所需要的html、css、js和图片等后就会开始解析并生成两个内部的数据结构:DOM树和渲染树。
DOM树是表示页面的结构,渲染树是表示DOM节点如何显示。
DOM树不必多说,渲染树会结合DOM树和DOM节点对应的CSS样式去理解页面上每个元素的样式(比如内外边距,边框,位置等),去构建一个渲染树。渲染树完成后浏览器就开始绘制(paint)页面元素。
关于这部分可以看这个链接渲染树构建、布局及绘制
当DOM树的结构和CSS产生变化影响到页面布局或元素的几何属性的时候,浏览器会重新构建渲染树,重新构建渲染树的过程就叫做重排(reflow)
,重排完成后,浏览器会重新绘制受到影响的部分到屏幕中,这个过程叫做重绘(repaint)
。
并不是所有的变化都会影响到页面布局和元素的几何属性(即发生重排),下面的情况会发生重排:
- 添加和删除可见的DOM元素。
- 元素位置改变。
- 元素尺寸改变(内外边距,边框厚度,宽高等)。
- 内容改变(文本改变,图片尺寸改变)。
- 浏览器窗口尺寸变化。
- 页面渲染器初始化。
不改变页面布局和元素几何属性的变化(比如背景色变化),只会发生一次重绘,并不会发生重排。
减少重绘与重排
因为重绘和重排需要大量计算,会影响页面的响应速度,所以我们应该尽量减少和避免重绘和重排。
改变样式
看下面这段代码:
1 | var el = document.getElementById('mydiv'); |
这样添加样式,每一次都会改变元素的几何属性,在一些旧版浏览器中可能会引起三次重排(现代浏览器会做优化处理,发生一次重排),所以可以优化一下代码,合并样式一次性修改:
1 | var el = document.getElementById('mydiv'); |
这样修改只会引起一次重排,更为高效。还有一种做法就是为想要修改的部分添加一个class
使用css一次性修改。
批量修改DOM
如果我们需要对DOM进行一系列操作的时候,可以通过下面的做法来减少重绘和重排:
- 使元素脱离文档流。
- 对其进行操作。
- 把元素带回文档中。
这样如果我们在第二步进行多次操作时,也只会在第一步和第三步触发两次重排。
有三种基本方法可以使DOM脱离文档流:
- 隐藏元素,修改,重新显示。
- 使用文档片断(document fragment),在当前DOM外构建一个子树,再把它插入文档中。
- 把原始元素拷贝到脱离文档的节点中,修改后在把原始元素替换掉。
用代码来说明三种方法:
比如现在我们有一个ul
列表,我们用一个方法appendDataElement()
往列表中添加li
。
1 | //要操作的列表 |
如果我们不使用任何方法的话,每插入一个li
就会触发一次重排,这样是很影响性能的。所以我们可以使用上面的三种方法。
方法一:
1 | var ul = document.getElementById('mylist'); |
方法二:
1 | var fragment = document.createDocumentFragment(); |
方法三:
1 | var old = document.getElementById('mylist'); |
文章中是推荐我们使用第二种方法,因为这种方法本来设计的初衷就是为了解决这类任务的(更新和移动节点)。而且这种方法只触发一次重拍,只访问一次DOM节点。
既然说到了重绘和重排,顺便就说一下动画,在动画中我们可以尽量使用transform
和opacity
,因为他们会不会触发重绘。具体可以看看这两篇文章: