从图中可以看出,左边输入的是 HTML、CSS、JavaScript 数据,这些数据经过中间渲染模块的处理,最终输出为屏幕上的像素。这中间的渲染模块就是我们要讨论的主题。
渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线。
每个子阶段都有输入内容、处理过程、输出内容。
1、⭐️构建DOM树
因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。
要让DOM节点拥有正确的样式,这就需要样式计算了⬇️
2、⭐️样式计算
- 把CSS转化为浏览器能够理解的结构——styleSheets
- 转换样式表中的属性值,使其标准化
- 计算出DOM树中每个节点的样式。
把CSS转化为浏览器能够理解的结构——styleSheets
和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式。
所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。
在控制台中输入 document.styleSheets,然后就看到如下图所示的结构:
渲染引擎会把获取到的 CSS 文本全部转换为 styleSheets 结构中的数据,并且该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。
转换样式表中的属性值,使其标准化
计算出DOM树中每个节点的样式
这就涉及到 CSS 的继承规则和层叠规则了。
继承规则:CSS 继承就是每个 DOM 节点都包含有父节点的样式(开发者工具的element tab中可以看到)
层叠规则:合并来自多个源的属性值(行内样式、页内样式、外部样式)
3、⭐️布局阶段
Chrome 在布局阶段需要完成两个任务:创建布局树和布局计算。
创建布局树——构建一棵只包含可见元素布局树
布局计算——计算布局树中节点的坐标位置并保存在布局树中
4、⭐️分层
分层:渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。
浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
那么需要满足什么条件,渲染引擎才会为特定的节点创建新的图层呢?
第一点,拥有层叠上下文属性的元素会被提升为单独的一层。
第二点,需要剪裁(clip)的地方也会被创建为图层。
5、⭐️图层绘制
渲染引擎实现图层的绘制会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:
从图中可以看出,绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。
6、⭐️栅格化操作
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。
如上图所示,当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程,那么接下来合成线程是怎么工作的呢?
合成线程会将图层划分为图块(tile)
然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示:
通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
相信你还记得,GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。具体形式你可以参考下图:
从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。
7、⭐️合成和显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。
总结
现在已经分析完了整个渲染流程,从 HTML 到 DOM、样式计算、布局、图层、绘制、光栅化、合成和显示。用一张图来总结下这整个渲染流程:
结合上图,一个完整的渲染流程大致可总结为如下:
- 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。