回流、重绘的概念

页面渲染:
https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work#layout
回流与重绘:
https://segmentfault.com/a/1190000017329980/

何时触发回流、重绘

回流:
当页面布局和几何信息发生变化的时候。如增删DOM,元素位置变化,滚动条出现,窗口尺寸变化等。

重绘:
颜色,透明度等不影响布局的修改仅触发重绘。

回流一定触发重绘,重绘不一定回流。

浏览器优化机制

每次重排都要造成额外的计算消耗,因此现代浏览器会通过队列化修改并批量执行来优化重排过程。
浏览器会将修改操作放入队列,直到一段时间后或操作达到某个阈值,才清空队列。

但当你获取布局信息的操作的时候,会强制渲染队列刷新,导致回流重绘,因此尽量避免使用以下属性:
https://gist.github.com/paulirish/5d52fb081b3570c81e3a

减少回流、重绘

1. 最小化重绘和回流

当我们一定会导致回流时,最好的办法就是减少它发生的次数。可以合并对DOM多次的修改:

  1. // 优化前:修改了三个样式属性,每一个都影响元素的几何结构
  2. // 导致三次回流
  3. const el = document.getElementById('test');
  4. el.style.padding = '5px';
  5. el.style.borderLeft = '1px';
  6. el.style.borderRight = '2px';
  7. // 优化后:仅一次回流
  8. // 使用cssText
  9. const el = document.getElementById('test');
  10. el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
  11. // 修改CSS的class
  12. const el = document.getElementById('test');
  13. el.className += ' active';

2. 批量修改DOM

当我们对DOM进行一系列修改时,可以通过以下步骤减少回流、重绘次数:

  1. 使元素脱离文档流
  2. 对其进行多次修改
  3. 将元素带回文档中

因此我们可以考虑以下方法:

  • display 隐藏元素,修改后再重新展示
  • 使用文档片段 document.createDocumentFragment() 在当前DOM外构建一个子树,再拷贝回文档
  • 将原始元素拷贝到一个脱离文档流的节点中,修改后再替换原始元素

但是现代浏览器大都会使用队列来储存多次修改,当你不去主动获取布局信息时,会等待js执行完成再进行回流与重绘。因此上述这些优化方法意义不大,但需理解。

3. 避免触发同步布局事件

上文提到,当我们访问元素一些属性时,会导致浏览器强制清空优化的队列,强制同步布局导致回流:

  1. for (let i = 0; i < list.length; i++) {
  2. list[i].style.width = box.offsetWidth + 'px';
  3. }

上述代码存在着一个很严重的性能问题。每次循环时,都读取了 box.offsetWidth 。如果没有这一步,浏览器会在for循环过程中将操作都放入队列中,等js执行完后再进行重排。

但是为了获取到 box.offsetWidth ,浏览器必须先让上次循环中的样式更新生效,然后重新计算布局信息获取到 box.offsetWidth 的值。使得每一次的循环都强制浏览器刷新了队列。

我们可以优化成如下代码:

  1. const boxWidth = box.offsetWidth;
  2. for (let i = 0; i < list.length; i++) {
  3. list[i].style.width = boxWidth + 'px';
  4. }

4. 复杂动画可以使用绝对定位让其脱离文档流

复杂的动画经常会引起回流重绘,因此我们可以使用绝对定位,让其脱离文档流,使影响面变小,提升性能。

5. css3硬件加速

将浏览器渲染过程交由GPU处理而不是浏览器自带的较慢的渲染器处理。

触发硬件加速的css属性:

  • transform
  • opacity
  • filters
  • Will-change

我们可以直接用 transform: translateZ(0); 写个空的transform 来开启css3硬件加速。

但注意不要滥用硬件加速:

  • 使用过多会导致内存占用较大,出现性能问题
  • GPU渲染字体会导致抗锯齿无效,动画结束时不关闭硬件加速,可能会产生字体模糊。