学习记录整理
任何一个网站的页面展示离不开HTML、CSS、JavaScript,也正是这三者,构成了美丽的web页面,那么在浏览器中,是如何将3者完美地展示出来呢?这就是浏览器的渲染。

HTML: 是由标记和文本组成,是页面要显示的文字内容
CSS:又称为层叠样式,用于对html的内容进行更为丰富的描绘(在没有使用css之前就只能用html实现,但html支持不够多,且不够灵活)
Javascript:又称js,可以让html页面内容“动”起来,更为灵活地控制处理html、css。
在理解了三者的意义后,我们开始正题,理解浏览器渲染的详细过程。

渲染机制很复杂,在过程中会分为很多个子阶段,按照渲染的时间顺序可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、栅格化和合成。而每一个子阶段又包含:输入内容、处理过程、输出内容。
一、构建DOM树阶段(html是有父子层次结构的,有html\head头、有body内容)浏览器是无法直接理解html标记内容,需要转为DOM树之后,才能正常输出显示


DOM树结构跟html代码层次结构显示上是一样的,不一样的地方是DOM树是存在内存里面,可以由js动态修改。(可以使用“开发者工具”,选择“Console”标签来打开控制台,在控制台里面输入“document”后回车,就能看到一个完整的 DOM 树结构)
二、样式计算阶段(生成dom树之后,只能确认html内容显示顺序/位置,还要进行计算样式才能描绘得更好看),具体会做以下3个处理
A、获取样式来源:
- 通过 link 引用的外部 CSS 文件
- style标记内的 CSS
- 元素的 style 属性内嵌的 CSS

和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets(同样可以使用开发者的Console工具,输入document.styleSheets回车后查看styleSheets结构)

从图中能看到很多种样式,且三种来源都有(styleSheets结构不是这章的重点)只需要知道渲染引擎会把获取的CSS文本全转为styleSheets结构中的数据,跟DOM树一样,可供js修改样式提供了基础。
B、转换样式表中的属性值,使其标准化(css转变成可以理解的结构后,就需要对属性值进行转换了,先看一段css样式)
body { font-size: 2em }
p {color:blue;}
span {display: none}
div {font-weight: bold}
div p {color:green;}
div {color:red; }
2em、blue、bold人类容易理解、但渲染引擎不容易理解(就像ip与域名一样),所以需要将所有值转换为渲染引擎更容易理解、能进行运算的具体数值。

C、计算出DOM树中每个节点的具体样式(能进行css属性具体数值后,就要对dom树一一赋样式值了)这就涉及到了CSS《继承规则》《层叠规则》。
继承规则:每个DOM节点都包含父节点的样式。

可以通过开发者工具的element的style标签,加深对继承的理解

- 首先,可以选择要查看的元素的样式(位于图中的区域 2 中),在图中的第 1 个区域中点击对应的元素元素,就可以了下面的区域查看该元素的样式了。比如这里我们选择的元素是p标签,位于html.body.div这个路径下面。
- 其次,可以从样式来源(位于图中的区域 3 中)中查看样式的具体来源信息,看看是来源于样式文件,还是来源于 UserAgent 样式表。这里需要特别提下 UserAgent 样式,它是浏览器提供的一组默认样式,如果你不提供任何样式,默认使用的就是 UserAgent 样式。
- 最后,可以通过区域 2 和区域 3 来查看样式继承的具体过程。
层叠规则:css的基本特征,定义了如何合并多个来源的属性值算法,也是css的核心,css全称“层叠样式表”正是强调了这一点(此次不多介绍,自行搜索)
总之,样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。

三、布局阶段(有了DOM树与树元素的样式还不足以显示页面,还需要计算出DOM树元素的几何位置信息)这一个计算过程称为布局。
Chrome在此阶段有两个任务:创建布局树与布局计算。创建布局树简单点说就是把DOM树与ComputedStyle(样式计算结果)合成一个新的结果集(即布局树)

在创建布局树的工作如下:
- 遍历 DOM 树中的所有可见节点,并把这些节点加到布局中;
- 不可见的节点会被布局树忽略掉,如 head 标签下面的全部内容,再比如 body.p.span 这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。
布局计算:就是计算出HTML标记的具体位置信息,计算过程复杂,放在后面再说明(目前:在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容)
四、分层(有了布局树,且具体位置信息也计算出来了,还是未能绘制页面,还要进行DOM树节点分层)
页面中有很多复杂的效果(3D变换、页面滚动、用z-indexing做z轴排序等 )为了实现这些效果,渲染引擎还特定的节点生成专用的图层,生成一个对应的图层树(Layer Tree)这就跟ps中的层是一样的概念,多个图层叠加一起构成了最终的一个图片


- 通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。如上图中的 span 标签没有专属图层,那么它们就从属于它们的父节点图层。但不管怎样,最终每一个节点都会直接或者间接地从属于一个层。
渲染引擎是如何创建新层呢?有以下任意一种情况都会创建新层。
第一点:拥有层叠上下文属性的元素(position、z-index、filter、opacity…)会被提升为单独的一层,更多层叠上下文可见参考。

第二点:需要剪裁(clip)的地方也会被创建为图层(如下代码与说明图)
<style>
div {
width: 200;
height: 210;
padding:10px;
border:1px solid #ff0000;
}
</style>
<body>
<div >
<p>所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p>
<p>从上图我们可以看到,document层上有A和B层,而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
<p>图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p>
</div>
</body>

- 出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。你可以参考下图:

五、图层绘制(构建完图层树之后,就要对每一层图层树进行绘制)
渲染引擎会像画图一样,将绘制拆分与很多小的绘制指令,组成待绘制序列表

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

绘制列表只是记录指令的列表,实际上操作是由渲染引擎中《合成线程》来完成,当绘制列表准备好之后,主线各会把绘制列表提交(commit)给《合成线程》 来处理

六、栅格化(raster)操作

一个网页可能会很长,不只是电脑当前屏幕这一点内容,上下滚动还会有很大部分的内容,一般把浏览器当前可见的位置称为视口(如首屏/屏幕显示内容)通过视口,用户只会看到页面很小的一部分,所以在绘制时候,不会绘制所有的html内容,而是由《合成线程》将图层划分为图块(tile)。
合成线程会按照视口位置,优先将《可见图块》生成位图,而生成位图的操作是由栅格化来执行的,所谓栅格化,就是将图块转换为位图。

- 通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
- 渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中

七、合成与显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程的viz组件会接收《合成线程》的DrawQuad命令,然后将其页面内容绘制到内在中,最后将内存数据显示到屏幕上。

- DOM:渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- Style:渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- Layout:创建布局树,并计算元素的布局信息。
- Layer:对布局树进行分层,并生成分层树。
- Paint:为每个图层生成绘制列表,并将其提交到合成线程。
- tiles-raster:合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- Drawquad:合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- Display:浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
整体渲染流水完成后,再补充一下几个概念“重排”、“重绘”、“合成”
重排:通过js或css修改位置属性(width、height、padding…)就会触发重新布局,《Style-样式计算》后续子阶段都要重走,这是要更新完整的渲染流水线,开销最大
重绘:通过js或css修改背景颜色(backgroud、border…)就会触发html元素的重新绘制,《Paint-图层绘制》子阶段都要重走
合成:如果你更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的《tiles合成》操作


