本篇介绍一下JavaScript DOM可能是最耗时间的重绘和重排问题。
浏览器下载完页面中的所有组件——HTML标记,JavaScript,CSS,图片之后会解析并生成两个内部数据结构:DOM树 、渲染树。
重排:
当DOM元素的变化影响了元素的几何属性(宽、高等)——比如改变边框宽度或给段落增加文字,导致行数增加,浏览器需要重新计算元素的几何属性,同样其他元素的几何属性也会受到影响。浏览器会重新构造渲染树。
重绘:
重排完,浏览器会重新绘制受影响的部分到屏幕中。
重排什么时候发生?
- 添加或删除可见的DOM元素
- 元素位置改变
- 元素尺寸改变 (包括内、外边距等属性)
- 内容改变 (图片被不同尺寸的图片代替)
- 页面渲染器初始化
- 浏览器窗口尺寸改变(不间断地改变浏览器窗口大小,导致UI反应迟钝(某些低版本IE下甚至直接挂掉),这就是重绘重排问题导致的)
渲染树变化的排队与刷新
阅读下列代码:
var e = document.getElementById('myDiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
感觉上述代码执行了三次的重排、重绘,但其实大部分现在的浏览器都为此做了优化,只会触发一次重排。由于每次重排都会产生计算消耗,大部分浏览器都通过队列修改并批量执行来优化重排过程。
但是我们可能会不知不觉的强制刷新队列,并要求任务立刻执行。
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop, scrollLeft, scrollWidth, scrollHeight
- clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle()
上面的方法和属性需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的待处理变化并触发重排以返回正确的值。所以,在修改样式的时候,应该避免使用上面列出的属性。
最小化重排和重绘
为了减少重绘,重排发生的次数,应该合并多次对DOM和样式的修改,一次性处理掉。
合并所有的改变然后一次处理,这样只会修改DOM一次,可以使用cssText属性实现:
el.style.cssText += 'border-left:1px; border-right:2px; padding:5px; ';
批量修改DOM
通过以下可以减少重绘和重排的次数:
- 使元素脱离文档流
- 对其应用多重改变
- 把元素带回文档中
这里只会触发两次重排第一步和第三步
有三种方法可以使DOM脱离文档:
- 隐藏元素,应用修改,重新修改
- 使用文档断片
- 将原始元素拷贝到一个脱离文档流的节点中,修改副本,完成后在替换原始元素
尽可能使用文档断片(document fragment)
文档断片是个轻量级的document对象,设计的初衷就是为了完成更新和设计节点。使用文档断片时,当你附加一个断片到节点中时,实际上被添加的是该断片的子节点。
var fragment = document.createDocumentFragment();
var oP = document.createElement('p');
oP.innerText = '文档断片';
fragment.appendChild(oP);
document.appendChild(fragment);
上述代码只触发了一次重排。
参考资料——《高性能JavaScript》