重排与重绘

重排和重绘

浏览器构建页面布局

  • 构建DOM树(parse):渲染引擎解析html文档,首先将标签转换成DOM树中的DOM node,然后生成内容树(Content Tree/DOM Tree)。同时,也会进行CSS解析,构建CSS Rules Tree
  • 构建渲染树(render tree):将 CSS Rules Tree依附于DOM Tree 之上,形成Render Tree
  • 构建布局渲染树(reflow/layout):从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的精确坐标。
  • 绘制渲染树(paint/repaint):遍历渲染树,使用UI层来绘制每个节点。

重绘(repaint / redraw)

重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。

触发重绘的条件:改变元素外观属性。如:color,background-color等。

重排(重构/回流/reflow)

当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。

重排发生的根本原理就是元素的几何属性发生了改变。如:

  • 添加或删除可见的DOM元素
  • 元素位置改变
  • 元素本身的尺寸发生改变
  • 内容改变
  • 页面渲染器初始化
  • 浏览器窗口大小发生改变
  • 读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE) )

重排和重绘的关系

在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。

所以:重排必定会引起重绘,但是重绘不一定引起重排。

重绘和重排的代价很高,耗时,会造成浏览器的卡顿

优化

  • 浏览器自己的优化:浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

  • 开发者自己的优化:尽量减少重绘重排的次数,合并多次的DOM修改和样式修改,减少对style样式的请求

    • 直接改变元素的className
.active {
    padding: 5px;
    border-left: 1px;
    border-right: 2px;
}
// javascript
var el = document.querySelector('.el');
el.className = 'active';
  • display:none;先设置元素为display:none;然后进行页面布局等操作;设置完成后将元素设置为display:block;这样的话就只引发两次重绘和重排;
let ul = document.querySelector('#mylist');
ul.style.display = 'none';
appendNode(ul, data);
ul.style.display = 'block';
  • 减少重绘重排
var el = document.querySelector('.el');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';

//替换为
var el = document.querySelector('.el');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px';
  • 如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document;
var fragment = document.createDocumentFragment();

var li = document.createElement('li');
li.innerHTML = 'apple';
fragment.appendChild(li);

var li = document.createElement('li');
li.innerHTML = 'watermelon';
fragment.appendChild(li);

document.getElementById('fruit').appendChild(fragment);
  • 将原始元素拷贝到一个独立的节点中,操作这个节点,然后覆盖原始元素
let old = document.querySelector('#mylist');
let clone = old.cloneNode(true);
appendNode(clone, data);
old.parentNode.replaceChild(clone, old);
  • 缓存布局信息,减少对style的多次请求
div.style.left = 1 + div.offsetLeft + 'px';
div.style.top = 1 + div.offsetTop + 'px';

//替换为
current = div.offsetLeft;
div.style.left = 1 + ++current + 'px';
div.style.top = 1 + ++current + 'px';
  • 尽量不要使用table布局。
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!