浏览器视角看——重绘重排

Posted 胖鹅68

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器视角看——重绘重排相关的知识,希望对你有一定的参考价值。

一、参考

  1. 强烈推荐 —— 李兵老师的 《浏览器工作原理与实践》

二、 渲染引擎概述

2.1 渲染流程各个阶段

  1. 渲染进程将 html 内容转换为能够读懂的 DOM 树结构。

  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。

  3. 创建布局树,并计算元素的布局信息。

  4. 对布局树进行分层,并生成分层树。

  5. 为每个图层生成绘制列表,并将其提交到合成线程。

  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。

  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

三、渲染引擎阶段概述

3.1 构建 DOM 树

为什么要构建 DOM 树呢?
这是因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。

DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 javascript 来查询或修改其内容

3.2 样式计算

1. 把 CSS 转换为浏览器能够理解的结构

CSS 样式来源主要有三种:

  • 通过 link 引用的外部 CSS 文件
  • <style>标记内的 CSS
  • 元素的 style 属性内嵌的 CSS

CSS为什么要转换?

  • 和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式
  • 所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。

document.styleSheets
CSS 文本全部转换为 styleSheets 结构中的数据,并且该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础
可以参考阮一峰老师的 —— CSS操作

2. 转换样式表中的属性值,使其标准化

如 2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

3. 计算出 DOM 树中每个节点的具体样式

如何计算呢?——CSS 的继承规则和层叠规则

  • 需要特别提下 UserAgent 样式,它是浏览器提供的一组默认样式,如果你不提供任何样式,默认使用的就是 UserAgent 样式。
  • CSS 继承就是每个 DOM 节点都包含有父节点的样式
  • 层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法

3.3 布局阶段

计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局

1. 创建布局树

你可能注意到了 DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树

2. 布局计算

计算布局树节点的坐标位置了

3.4 分层

为什么要分层?

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。

如果你熟悉 PS,相信你会很容易理解图层的概念,正是这些图层叠加在一起构成了最终的页面图像。

那么需要满足什么条件,渲染引擎才会为特定的节点创建新的图层呢?(满足之一)

第一点,拥有层叠上下文属性的元素会被提升为单独的一层。

层叠上下文?
https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context

第二点,需要剪裁(clip)的地方也会被创建为图层。

3.5 绘制

渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表

绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。

3.6 分块

合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512

3.7 光栅化

合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图

图块是栅格化执行的最小单位

渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的

3.8 合成和显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。

浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad命令,然后根据DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

四、重排、重绘、合成

4.1 重排

浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。
无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

4.2 重绘

没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。
相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

4.3 合成

渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成
效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率

五、如果下载 CSS 文件阻塞了,会阻塞 DOM 树的合成吗?会阻塞页面的显示吗?

  1. 那么DOM解析器会先执行JavaScript脚本,执行完成之后,再继续往下解析。
<html>
  <body>
      极客时间
      <script>
      document.write("--foo")
      </script>
  </body>
</html>

  1. 当解析到JavaScript的时候,会先暂停DOM解析,并下载foo.js文件,下载完成之后执行该段JS文件,然后再继续往下解析DOM。这就是JavaScript文件为什么会阻塞DOM渲染。
<html>
  <body>
      极客时间
      <script type="text/javascript" src="foo.js"></script>
  </body>
</html>

  1. 当我在JavaScript中访问了某个元素的样式,那么这时候就需要等待这个样式被下载完成才能继续往下执行,所以在这种情况下,CSS也会阻塞DOM的解析。
<html>
  <head>
      <style type="text/css" src = "theme.css" />
  </head>
  <body>
      <p>极客时间</p>
      <script>
          let e = document.getElementsByTagName('p')[0]
          e.style.color = 'blue'
      </script>
  </body>
</html>

结论: 所以JS和CSS都有可能会阻塞DOM解析

以上是关于浏览器视角看——重绘重排的主要内容,如果未能解决你的问题,请参考以下文章

前端性能优化之重排和重绘

前端性能优化之重排和重绘

js中的重绘与重排的区别

页面重绘重排

禁用浏览器的重排\重绘

浏览器的重绘与重排