重排重绘

发布于 2022-11-20  38 次阅读


前置知识

浏览器渲染机制

浏览器采用流式布局模型(Flow Based Layout)

浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就产生了渲染树(Render Tree)。

有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。

由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花3 倍于同等元素的时间,这也是为什么要避免使用 table 布局的原因之一。

怎么理解回流(重排)和重绘?什么场景下会触发?如何避免?

  • 是什么
    • 回流(重排)
      • 指的是处于文档流中 DOM 的尺寸大小、位置或者某些属性发生变化时,导致浏览器重新渲染部分或全部文档的情况
    • 重绘
      • 指的是当页面中的元素不脱离文档流,而简单地进行样式的变化,比如修改颜色、背景等,浏览器重新绘制样式
  • 浏览器优化
    • 现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。
  • 何时触发
    • 回流(重排)
      • 回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:
        • 添加或删除可见的DOM元素
        • 元素的位置发生变化
        • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
        • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
        • 页面一开始渲染的时候(这避免不了)
        • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
      • 获取一些特定属性的值
        • offsetTop、offsetLeft、 offsetWidth、offsetHeight
        • scrollTop、scrollLeft、scrollWidth、scrollHeight
        • clientTop、clientLeft、clientWidth、clientHeight
        • width、height
        • 原因:
          • 这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流。除此还包括 getComputedStyle、getBoundingClientRect() 方法,原理是一样的
          • 所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列
    • 重绘
      • 触发回流一定会触发重绘
        • 可以把页面理解为一个黑板,黑板上有一朵画好的小花。现在我们要把这朵从左边移到了右边,那我们要先确定好右边的具体位置,画好形状(回流),再画上它原有的颜色(重绘)
      • 颜色的修改
      • 文本方向的改变
      • 阴影的修改
      • 背景的改变
  • 如何避免
    • HTML
      • 避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。
    • CSS
      • 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
      • 如果需要设置动画效果,最好将元素脱离正常的文档流。
        • 比如将动画效果应用到 position 属性为 absolute 或 fixed 的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame。
      • 避免使用 CSS 表达式(例如:calc())。
      • 开启 GPU 加速(transform: translateZ(0)
    • JS
      • 合并多次对 CSS 样式的修改,改成一次处理
      <body> <div id="box">文字内容</div> <script> var box = document.getElementById("box"); // box.style.width='100px'; // box.style.height="100px"; // box.style.backgroundColor='red'; box.style.cssText += "width:100px;height:100px;background-color:red"; </script> </body>
      • 避免频繁操作样式,最好将样式列表定义为 class 并一次性更改 class 属性。
      <style> .box { width: 100px; height: 100px; background-color: red; } </style> <body> <div id="box">文字内容</div> <script> var box = document.getElementById("box"); box.className = "box"; </script> </body>
      • 避免频繁操作 DOM ,创建一个 documentFragment ,在它上面应用所有 DOM 操作,最后再把它添加到文档中。
      <body> <div id="box"></div> <script> var box = document.getElementById("box"); var fragment = document.createDocumentFragment(); //创建虚拟的节点对象 for (var i = 0; i < 10; i++) { var span = document.createElement("span"); span.index = i; span.style.display = "inline-block"; span.style.width = "20px"; span.style.height = "20px"; span.style.backgroundColor = "red"; span.innerText = span.index; fragment.appendChild(span); } box.appendChild(fragment); </script> </body>
      • 可以先为元素设置为不可见:display: none,操作结束后再把它显示出来。
      • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
      • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素 及后续元素频繁回流。
最后更新于 2023-07-20