重绘和重排
网页渲染
“生成布局”(flow)和"绘制"(paint)这两步,合称为"渲染"(render)。
重绘 repint
重绘:某种操作改变了某个元素的外观,但并未改变这个元素的布局,从而需要重新绘制。例如对 outline
、visibility
、background
、color
的改变。重绘不一定会引起重排。
重排/回流 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
属性为 absolute
或 fixed
的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。
非必要不可见
只在必要的时候,才将元素的 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()
它指定只有当一帧的末尾有空闲时间,才会执行回调函数。