浏览器渲染过程、回流、重绘

浏览器渲染过程、回流、重绘

当浏览器收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程得消息队列,在事件循环机制得作用下,渲染主线程将取出消息队列中得渲染任务,开启渲染流程。整个渲染分为多个阶段:html解析样式计算布局分层绘制分块光栅化,每个阶段都有明确的输入输出,上一个阶段的输出作为下一个阶段的输入。渲染流程如下:

渲染流程

主线程主要就是把绘制信息弄出来,然后交给合成线程来真正画出来(主要是 GPU 操作)。

渲染过程分解

解析 HTML 文档

  1. 解析过程中遇到 HTML 元素会解析 HTML 元素,最终生成 DOM 树
  2. 解析过程中遇到style标签link标签行内样式等 CSS 样式,会解析 CSS 会生成 CSSOM 树

    CSS 不会阻塞 HTML 解析

    如果主线程解析 HTML 过程中遇到了 link,此时外部的 CSS 还没有下载解析好,主线程不会等待,继续解析后面的 HTML。因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因.

    JS 会阻塞 HTML 解析

    如果主线程遇到了 script,会停止解析,转而等待 JS 文件下载好,并将全局的代码解析执行完成后,才能继续解析 HTML。这是因为 JS 代码里面执行过程中可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。

上面的步骤完成了,就会得到 DOM 树和 CSSOM 树(浏览器的默认样式、内部样式、外部样式、行内样式都会包含在 CSSOM 树中)。

样式计算

主线程会遍历 DOM 树,依次为树的每个节点计算出最终的样式,称为computed style,最终得到一颗带有样式的 DOM 树

布局

依次遍历 DOM 树的每个节点,计算出几何位置。宽高、相对位置之类的,最终生成布局树

分层

主线程会使用一套复杂的策略对整个布局树进行分层。

  1. 分层的好处,将来某一层发生改成,仅仅只用对这一层进行处理即可,从而提升效率。
  2. 滚动条、堆叠上下文、transform、opacity 等样式都会影响分层结果,也可以通过will-change属性更大程度的影响分层结果。

CSS 另一个名字“层叠样式表”,浏览器渲染出来的页面,其实一层一层的堆叠上去的。

绘制

主线程会对每个图层单独产生绘制指令集,用于描述这一层内容应该如何画出来。完成绘制过后,主线程会将每个图层的绘制信息交给合成线程

分块

合成线程首先对每个图层进行分块,将其分层很多小区域。分块是多线程来进行,N 个分块器进行拆分。

光栅化

合成线程将块的信息交给 GPU,以极高的速度完成光栅化。

光栅化

GPU 会开启多线程来完成光栅化,并且优先处理靠近视口区域的块。

合成线程拿到每个层、每个块的位图后,生成一个个指引的信息,里面包含了每个位图在屏幕的哪个位置,以及会考虑旋转、缩放等变形。

变形发生在合成线程,与主线程渲染无关,这就是transform效率高的根本原因。

合成线程会把指引信息交给 GPU,GPU 进行产生系统调用,最终提交给 GPU 硬件,完成最终的屏幕成像。一句话:合成线程把位图的信息交给 GPU,最终画出来。

回流

回流

回流 reflow 主要的本质是重新计算布局树

当我们进行影响布局的操作后,需要重新计算布局树,会引发 layout。

影响布局的操作:增删 DOM 节点,修改元素的宽高、字体大小改变、border 变了等操作。

为了避免连续的多次操作导致布局树重复计算,浏览器会合并这些操作,当 JS 代码全部完成过后再统一计算,所以,改动属性造成回流(reflow)是异步操作。

注意:因为回流是异步操作,当 JS 获取布局属性时候,很有可能无法获取最新的布局信息。在浏览器反复权衡下,最终决定获取属性(scrollTop、offsetLeft、offsetWidth 等)立即回流(reflow)。

重绘

重绘

重绘的本质就是重新根据分层信息计算绘制指令,是在 layer 这一步。
当改变了可见样式(背景、字体颜色),就需要重新计算,会引发 repaint。

有上图可以看出来 layer 在 layout 后面,reflow 就是重新计算布局树,在 layout 这一步,因此回流(reflow)一定会引起重绘(repaint)。

为什么 transform 效率高

为什么 transform 效率高

因为 transform 不会影响渲染主线程的步骤,它影响的只是渲染流程中最后一个步骤 draw 阶段。
由于 draw 阶段是在合成线程这种,所以 transform 的变化不会影响渲染主线程。

如何减少回流、重绘

  1. 尽量使用 CSS 属性的简写:用 boder 代替 border-width、border-style、border-color。
  2. 批量修改元素样式,采用 class
  3. 需要创建多个 DOM 时候,使用 documentFragment。
  4. 尽量不要在 for 循环中获取元素的位置或者大小位置,这也会引起回流,如果一定要操作,最好利用缓存。