前端4-1-6:浏览器的回流、重绘、优化

题外话:有时候呢,学着学着就卡机了>-<....又到了记小本本时刻。

目录

1、浏览器渲染大致流程

2、生成渲染树

3、回流(reflow)

3-1、定义

3-2、会导致回流的操作

3-3、常用且会导致回流的属性和方法

4、重绘(repaint)

4-1、定义

4-2、性能影响

4-3、浏览器会立刻清空队列的访问属性或方法

5、优化

5-1、CSS

5-2、JS

5-3、DocumentFragment


首先,先回顾下浏览器渲染过程吧!

1、浏览器渲染大致流程

  1. 处理 HTML 标记并构造 DOM 树。
  2. 处理 CSS 标记并构造 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 将各个几点绘制到屏幕上。/

2、生成渲染树

构建渲染树,浏览器需要做一下工作:

  1. 从 DOM 树的根节点开始遍历每个 可见 节点。
  2. 对于每个可见的节点,找到 CSSOM 树中对应的规则,并应用它们。
  3. 根据每个可见节点以及其对应的样式,组合生成渲染树。

第一步遍历节点的时候,需要知道什么节点是不可见的。

  • 一些不会渲染输出的节点。比如 script、meta、link等。
  • 某些节点通过 CSS 隐藏。注意 visibility: hidden 与 display: none 是不一样的。
  • 前者隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空框),而后者 (display: none) 将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分。
  • 注意: 渲染树只包含可见的节点

3、回流(reflow)

当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。

3-1、定义

元素的大小或者位置发生改变(当页面布局发生改变的时候),触发了重新布局导致渲染树重新计算布局和渲染。

  • 如添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化
  • 内容发生变化(如文本变化或图片被另一个不同尺寸的图片所代替)
  • 页面一开始渲染的时候(无法避免)

3-2、会导致回流的操作

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

3-3、常用且会导致回流的属性和方法

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

4、重绘(repaint)

当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘

4-1、定义

只改变自身样式,不会影响到其他元素。

  • 元素样式的改变(但宽高、大小、位置不变)
  • eg:   visibility、color、background-color等

注意:回流一定会触发重绘,而重绘不一定会回流

4-2、性能影响

  • 有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流
  • 现代浏览器会对频繁的回流或重绘操作进行优化:浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

4-3、浏览器会立刻清空队列的访问属性或方法

当你访问以下属性或方法时,浏览器会立刻清空队列:

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

因为队列中可能会有影响到这些属性或方法返回值的操作,即使你希望获取的信息与队列中操作引发的改变无关,浏览器也会强行清空队列,确保你拿到的值是最精确的。


5、优化

  • 减少对 render tree 的操作 【合并多次多DOM和样式的修改】
  • 减少对一些style信息的请求,尽量利用好浏览器的优化策略
  1. 添加css样式而不是利用js控制样式(我就是想到这种办法解决回流问题的)
  2. 尽量将需要改变DOM的操作一次完成
  3. 直接改变className,如果动态改变样式,则使用cssText(考虑没有优化的浏览器)
  4. 不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存
  5. 让元素脱离动画流,减少回流的Render Tree的规模
  6. 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位;
  7. 尽量不要使用表格布局,如果没有定宽表格一列的宽度由最宽的一列决定,那么很可能在最后一行的宽度超出之前的列宽,引起整体回流造成table可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。

5-1、CSS

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

5-2、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的操作一次完成
    let box = document.getElementById("box").style;
    // bad
    box.color = "red";    // 重绘
    box.size = "14px";    // 回流、重绘
    // good
    box.bord = '1px solid red'
  4. 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流
  5. 避免频繁操作 DOM,创建一个documentFragment,在它上面应用有 DOM 操作,最后再把它添加到文档中

5-3、DocumentFragment

  • 文档片段接口,表示一个没有父级文件的最小文档对象
  • 与 Document 最大的区别是因为 DocumentFragment 不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的(重新渲染) ,且不会导致性能等问题
  • 可以使用 document.createDocumentFragment 方法或者构造函数来创建一个空的 DocumentFragment。

猜你喜欢

转载自blog.csdn.net/weixin_44015669/article/details/115380786
今日推荐