重绘和重排

重绘和重排

网页渲染

“生成布局”(flow)和"绘制"(paint)这两步,合称为"渲染"(render)。

重绘 repint

重绘:某种操作改变了某个元素的外观,但并未改变这个元素的布局,从而需要重新绘制。例如对 outlinevisibilitybackgroundcolor 的改变。重绘不一定会引起重排

重排/回流 reflow

重排/回流:某种操作改变了某个元素、网页的一部分或整个网页的布局,其对于性能的影响更为严重重排必会导致重绘

重排触发机制

重排发生的根本原理就是元素的几何属性发生了改变,那么我们就从能够改变元素几何属性的角度入手

  • 添加或删除可见的 DOM 元素
  • 元素位置改变
  • 元素本身的尺寸发生改变
  • 内容改变
  • 页面渲染器初始化
  • 浏览器窗口大小发生改变

如何优化

浏览器自身优化

现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。

主要包括以下属性或方法:

offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
width、height
getComputedStyle()
getBoundingClientRect()

所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列

合并读操作/写操作

不要这样:

// bad
div.style.left = div.offsetLeft + 10 + "px";
div.style.top = div.offsetTop + 10 + "px";

读写分离合并:

// good
var left = div.offsetLeft;
var top  = div.offsetTop;
div.style.left = left + 10 + "px";
div.style.top = top + 10 + "px";

缓存

如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排。

不要一条条改变样式

不要一条条地改变样式,而要通过改变 class,或者 css text 属性,一次性地改变样式。

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// good 
el.className += " theclassname";

// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

使用离线 DOM

尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式。比如,操作 Document Fragment 对象,完成后再把这个对象加入DOM。再比如,使用 cloneNode() 方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。

善用 display:none

先将元素设为 display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。

善用 absolute 和 fixed

position 属性为 absolutefixed 的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。

非必要不可见

只在必要的时候,才将元素的 display 属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility:hidden 的元素只对重绘有影响,不影响重排。

虚拟 DOM

使用虚拟 DOM 的脚本库,比如 React 等。

善用 RAF、RIC

使用 window.requestAnimationFrame()window.requestIdleCallback() 这两个方法调节重新渲染。

(1)分离读写操作:

function doubleHeight(element) {
  var currentHeight = element.clientHeight;
  // 下一次重新渲染时执行
  window.requestAnimationFrame(function () {
    element.style.height = (currentHeight * 2) + 'px';
  });
}
elements.forEach(doubleHeight);

(2)页面滚动,推动到下一次执行 scroll 函数:

$(window).on('scroll', function() {
   window.requestAnimationFrame(scrollHandler);
});

(3)window.requestIdleCallback()

它指定只有当一帧的末尾有空闲时间,才会执行回调函数。

参考