页面加载渲染过程

1. 解析HTML代码并生产一个DOM树

2. 解析CSS文件,顺序为:浏览器默认样式 => 自定义样式 => 页面内样式

3. 生产渲染树。与DOM树不同的是渲染树受样式影响,不包括不可见节点

4. 根据渲染树,浏览器就会在屏幕上绘制出渲染树上的所有节点

什么是重绘

重绘是一个节点的外观发生改变的行为,例如改变color、outline等属性。浏览器会根据节点的新属性重新绘制,使节点呈现新的外观。重绘不会带来重新布局,并不一定伴随着重排。

什么是重排

当DOM变化影响了节点的几何属性,浏览器需要重新计算节点的几何属性,并且页面中其他节点的可能受影响,这样渲染树就发生了改变并重新构造渲染树。这个过程称为重排。

触发重排的操作:

1. DOM元素的几何属性变化
   当DOM的几何属性变化时, 渲染树中的相关节点就会失效, 浏览器会重新构建渲染树中失效的节点。而且, 当前元素的重排也许会带来相关元素的重排。例如, 容器节点渲染树改变时, 会触发子节点的重新计算, 也会触发后续兄弟节点的重排, 祖先节点需要重新计算大小, 最后, 每个元素可能都会进行重绘。可见, 重排一定会引起重绘, 并且因为重排的元素很多, 导致重排从性能上来说比重绘更差。一个元素的重排通常都会带来一系列反应, 甚至触发整个文档的重绘和重排, 性能代价是高昂的。

2. DOM树的结构变化

   当DOM树的结构变化时, 例如节点的增加, 减少, 删除, 也会触发重排。浏览器引擎渲染DOM树类似前序遍历, 也就是说当前元素不会影响前面已经遍历过的元素。所以, 如果在body前面插入一个元素, 就会导致整个文档的重新渲染, 而在其后插入一个元素, 就不会影响到前面元素的布局。

3.获取某些属性

   浏览器会对重排进行优化, 可能会等到有足够数量的变化发生, 或者等到一定时间, 或者等一个线程结束, 再一起处理。这样就只会发生一次重排。但如果渲染树直接发生变化, 当获取一些属性时, 浏览器为了取得正确的值也会触发重排。这样就使得浏览器的优化失效。这些属性包括: offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。所以,在多次使用这些值时应进行缓存。

4. 其他: 比如改变元素的某些样式, 调整浏览器窗口等等也会触发重排。

最小化重绘和重排

1. 样式集中改变,例如添加类名

2. 避免在document上直接进行频繁的DOM操作

3. 缓存layout属性值

4. 使用绝对位置定位页面上需要改变的节点,脱离文档流后对其应用多重改变,把元素带回文档中。

举例:

<ul id='dom'>
  <li>a</li>
  <li>b</li>
</ul>

在ul中再添加2个选项

var fragment = document.createDocumentFragment();

var ul = document.getElementById('dom');

var li1 = document.createElement('li');
li1.innerHTML = 'c';

var li2 = document.createElement('li');
li2.innerHTML = 'd';

fragment.appendChild(li1);
fragment.appendChild(li2);

ul.appendChild(fragment);

文档片段是个轻量级的document对象,它的设计初衷就是为了完成这类任务——更新和移动节点。文档片段的一个便利的语法特性是当你附加一个片断到节点时,实际上被添加的是该片断的子节点,而不是片断本身。只触发了一次重排,而且只访问了一次实时的DOM。

总结

重排和重绘是DOM编程中耗能的主要原因之一,平时涉及DOM编程时可以参考以下几点:


  1. 尽量不要在布局信息改变时做查询(会导致渲染队列强制刷新)
  2. 同一个DOM的多个属性改变可以写在一起(减少DOM访问,同时把强制渲染队列刷新的风险降为0)
  3. 如果要批量添加DOM,可以先让元素脱离文档流,操作完后再带入文档流,这样只会触发一次重排(fragment元素的应用)
  4. 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。