回流(Reflow)和重绘(Repaint)

848 阅读4分钟

回流(Reflow)重绘(Repaint) 什么时候会触发回流或重绘呢?

  • 当我们对dom 进行修改当时候会引发它外观(样式)上的改变时,就会触发回流或重绘。
  • 这个过程本质上还是因为我们对 DOM 的修改触发了渲染树(Render Tree)的变化所导致的

浏览器渲染页面的流程

  1. 1.根据 HTML 结构生成 DOM 树
  2. 根据 CSS 生成 CSSOM
  3. 将 DOM 和 CSSOM 整合形成 RenderTree
  4. 根据 RenderTree (渲染树)开始渲染和展示
  5. 遇到<script>时,会执行并阻塞渲染

回流(reflow):

  • Render Tree 中部分或全部, 因元素的尺寸、布局、隐藏等改变而需要重新构建,浏览器重新渲染的过程称为 回流。

  • 会导致回流的操作:

    • 页面首次渲染。
    • 浏览器窗口大小发生改变。
    • 元素尺寸或者位置发生改变。
    • 元素内容变化(文字数量或者图片大小发生改变)。
    • 元素字体大小的改变。
    • 添加或者删除可见DOM 元素。
    • 激活 CSS 伪类 (eg: :hover)。
    • 查询某些属性或调用某些方法。
  • 一些常用且会导致回流的属性和方法。

    • clientWidthclientHeightclientTopclientLeft
    • offsetWidthoffsetHeightoffsetTopoffsetLeft
    • scrollWidthscrollHeightscrollTopscrollLeft
    • scrollIntoView()scrollIntoViewIfNeeded()
    • getComputedStyle()
    • getBoundingClientRect()
    • scrollTo()

重绘(repaint)

  • 当页面中元素样式的改变并不影响b布局时(eg:colorbackground-color等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

回流重绘总结

  • 由此来看 重绘不一定导致回流,回流一定会导致重绘 前面我们说回流和重绘是会对dom进行修改,会消耗性能,所以我们要尽可能减少回流和重绘的次数。

那如何避免回流和重绘

  • 减少对 render tree 的操作 【合并多次多DOM和样式的修改】
  • 减少对一些style信息的请求,尽量利用好浏览器的优化策略
  • CSS
    1. 直接改变className. (把要修改的样式集中到一个 class 内统一修改);
    2. 避免使用 table 布局 (尽量不要使用表格布局,如果没有定宽表格一列的宽度由最宽的一列决定,那么很可能在最后一行的宽度超出之前的列宽,引起整体回流造成table可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。)
    3. 尽可能在DOM树的最末端改变class,尽可能在DOM树的里面改变class(可以限制回流的范围)
    4. 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位;
    5. 使用display:none技术,只引发两次回流和重绘;
  • JS
    1. 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。(而不是利用js控制样式)

    2. 不要经常访问会引起浏览器缓存队列的属性(上述那些浏览器会立刻清空队列的属性)。如果确实要访问,利用缓存。eg:

      // bad
      for (var i = 0; i < len; i++) {
        el.style.left = el.offsetLeft + x + "px";
        el.style.top = el.offsetTop + y + "px";
      }
      // good
      var x = el.offsetLeft,
          y = el.offsetTop;
      for (var i = 0; i < len; i++) {
        x += 10;
        y += 10;
        el.style = x + "px";
        el.style = y + "px";
      }
      
      
    3. 尽量将需要改变DOM的操作一次完成

      const container = document.getElementById('container')
      container.style.width = '100px'
      container.style.height = '200px'
      container.style.border = '10px solid red'
      container.style.color = 'red'
      //将这段代码用 类名去合并
      .basic_style {
        width: 100px;
        height: 200px;
        border: 10px solid red;
        color: red;
      }
      const container = document.getElementById('container')
      container.classList.add('basic_style')
      
      
    4. 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

    5. 避免频繁操作 DOM,创建一个documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。

      **DocumentFragment**,文档片段接口,表示一个没有父级文件的最小文档对象. 与Document最大的区别是因为DocumentFragment不是真实DOM树的一部分,它的变化不会触发DOM树的(重新渲染) ,且不会导致性能等问题。