什么是 reflow

# 什么是 reflow

reflow 本质就是重新计算 layout 树。 当进行了会影响布局数的操作后,需要重新计算布局树,会引发layout 为了避免连续的多次操作导致布局树反复计算浏览器会合并这些操作,当 js 代码全部完成后在进行统一计算。所以改动属性造成的 reflow 是异步完成的。 同理。当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。 浏览器在反复权衡下,最终决定获取属性 立即 reflow。

# 什么是 repaint

repaint 本质就是重新根据分层信息计算了绘制指令。 当改动了可见样式后,就需要重新计算,会引发 repaint 由于元素的布局信息也属于课件样式,所以 reflow 一定会引起 repaint

# 为什么 transform 的效率高

因为transform 既不会影响布局也不会影响绘制指令,他影响的只是渲染进程的一个 【draw】阶段,由于阶段在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响 transform 的变化。

# 浏览器的渲染流程

  • 解析 HTML
  • 样式计算
  • 布局
  • 分层
  • 生成绘制指令
  • 分块
  • 光栅化
  • 绘制

网络线程 ----- 网络通信 ----------------------------------------》 - -》HTML --》 - -》渲染任务(消息队列) 渲染主线程 -----------------》通过渲染任务的结果进行渲染 -----------------------》

首先,浏览器的网络线程会发送 http 请求,和服务器之间进行通信,之后将拿到的html封装成一个渲染任务,并将其传递给渲染主线程的消息队列。在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。

每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入。

# 解析 HTML

第一步,解析 HTML 生成 DOM 树。

在网络中传输的 HTML 文件是 0 和 1 的字节数据。浏览器接收到这些字节数据后,会将字节数据转换成字符串,也就是我们写的代码

字节数据 (010101) ------》 字符串(<p>123</p>)

当数据转为字符串后,浏览器会先将这些字符串通过词法分析转换为标记(token)-- 标记化 原因:浏览器不认识现在的字符串。标记化本质就是将字符串拆分成一块并给内容打上标记,便于理解块内的字符串是什么内容(打小抄,记笔记)。

字节数据 (010101) ------》 字符串(<p>123</p>) ----》标记化后 --->

--”<p>“

--“123”

--“</p>” 标记化后就能以此构建出 DOM 树。

字节数据 (010101) ------》 字符串(<p>123</p>) ----》标记化后 --->

--”<p>“

--“123”

--“</p>” 构建DOM树 《-- 分析DOM节点 《--

在解析HTML 过程中,我们可能会遇到 style、link 标签,这里会涉及 CSS 解析。

为了提高解析效率,浏览器再开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和 外部的 JS 文件。

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

渲染主线程 --》 解析 HTML ----------------------》CSSOM --》解析HTML --》DOM |解析完毕 预解析线程 --》快速浏览(link) ---------》解析CSS文件 --》 |link CSS文件 |下载完成 -网络线程 ---》下载 CSS ------》DONE

如果有JS文件会停止解析 HTML 等待 JS文件下好 并将代码解析执行完成后 才会继续解析 HTML。 这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。

第一步完成后会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。

# 样式计算

有了DOM树后,主线程会遍历得到的 DOM 树,依次为 树中的每个节点计算出它的最终样式(Computed Style) 在这一过程中,很多预设值会变成绝对值,如 red 会变为 rgb ,相对单位会变为绝对单位 如 em 会变为 px。

计算完成后,会得到一颗带有样式的 DOM 树 (DOM 数据和 CSSOM 数据合并)

# 布局

前面的步骤执行完毕后,渲染进程就已经知道页面的具体文档结构以及每个节点拥有的样式信息,但是还不足以渲染出页面的内容,还需要通过布局(layout) 来计算出每个节点的几何信息。

生成布局树的流程是:主线程会遍历刚刚构建的DOM 树,根据DOM 节点的计算样式计算出一个布局树(layout tree)。布局树上每个节点会有它在页面上的 x,y坐标以及盒子大小的信息。

实际生成的布局树可能和 DOM树不是一一对应的 如:display:none,没有办法计算坐标。 或伪元素,DOM树中不存在伪元素节点,但他们拥有坐标信息,会生成到布局树中。 还有一种匿名行盒与匿名块盒。

<body>
  <p>123</p> // 会生成匿名行盒 来装文字 
  123  // 会生成一个匿名块盒和一个匿名行盒 
  <p> // 不会生成 
     <span>
     123
     </span>
  </p>
</body>

# 分层

确认布局树后,会进行分层

为了确定哪些元素需要放在那一层,主线程需要遍历整颗布局树来创建一个 层次树

分层的好处就是,某个层改变后,仅会对该层进行后续处理,从而提升效率

滚动条、堆叠上下文、transform、opacity 等样式都会影响分层结果,也可以通过使用 will-change 属性来告诉浏览器分层。

# 生成绘制指令

分层结束后,就是生成绘制指令 主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。

类似于将画笔移动到 xx位置,放下画笔,绘制一条xx像素长度的线

注意:这一步只是生成 类似上面步骤的代码,并没有开始执行。 生成绘制指令集后,渲染主线程的工作暂时告一段落。接下来主线程会将每个图层的绘制信息提交给合成线程,剩余的工作将由合成线程完成。

渲染主线程 --》html解析 --》CSS 样式计算 --》布局 --》分层 --》生成绘制指令 |

其他线程 ---》 后续步骤 ···

# 分块

合成线程会对每个图层进行分块,将其划分为更多更小的区域。

分块的工作是由多个线程同时进行的。

# 光栅化

分块完成后,进入光栅化阶段。

光栅化就是将每个块变成每个像素点的rgb信息。

光栅化不是由合成线程来做,而是由合成线程将块的信息提交给 GPU进程,GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。

# 绘制

当所有的块都被栅格化后,合成线程会拿到每个层,每个块的位图,从而生成一个【指引 (quad)】信息。

合成线程计算出每个位图在屏幕上的位置,然后交给 GPU 最终呈现。

指引会表示出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放、变形等。

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

最终的流程就是

渲染主线程 --》parse --》 style --》layout --》layer --》 paint --》end|

其他线程 -----------------------------------------------------》tiling--》raster --》draw |

GPU --》 Screen

至此