浏览器工作原理——渲染流程(上)

2021年10月21日

总结

这一章的内容是介绍了浏览器如何让 HTML 变成可交互页面。根据谷歌官网文档的定义,将这一流程称为像素管道(pixel pipeline),课程中更加细致化,多介绍了几个步骤,这些步骤分别是:

  1. 构建 DOM 树
  2. 样式计算
  3. 布局
  4. 分层
  5. 绘制
  6. 栅格化
  7. 合成

本篇是上篇,主要是讲解从构建 DOM 树到布局的过程。

像素管道

像素管道是开发者可控的五个主要区域,也是渲染流程的关键点。
像素管道

  • JavaScript。一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如用 jQuery 的 animate 函数做一个动画、对一个数据集进行排序或者往页面里添加一些 DOM 元素等。当然,除了 JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如: CSS Animations、Transitions 和 Web Animation API。
  • 样式计算。此过程是根据匹配选择器(例如 .headline.nav > .nav__item)计算出哪些元素应用哪些 CSS 规则的过程。从中知道规则之后,将应用规则并计算每个元素的最终样式。
  • 布局。在知道对一个元素应用哪些规则之后,浏览器即可开始计算它要占据的空间大小及其在屏幕的位置。网页的布局模式意味着一个元素可能影响其他元素,例如 <body> 元素的宽度一般会影响其子元素的宽度以及树中各处的节点,因此对于浏览器来说,布局过程是经常发生的。
  • 绘制。绘制是填充像素的过程。它涉及绘出文本、颜色、图像、边框和阴影,基本上包括元素的每个可视部分。绘制一般是在多个表面(通常称为层)上完成的。
  • 合成。由于页面的各部分可能被绘制到多层,由此它们需要按正确顺序绘制到屏幕上,以便正确渲染页面。对于与另一元素重叠的元素来说,这点特别重要,因为一个错误可能使一个元素错误地出现在另一个元素的上层。

构建 DOM 树

从之前的章节中介绍过,渲染进程从网络进程中读取当前正在加载的文档的字节流,在接收数据的过程中,渲染器进程的 HTML 解析器会对接收到的数据进行解析,根据 HTML 文件的内容构造出易于解析,查找,和操作的数据结构———— DOM 树。 DOM 树既是文档结构的内部表示,也是暴露给 JavaScript 的 APIs。

HTML 解析

在本节课程中没有提到 HTML 解析器是如何解析的,但是在后续的内容中有详细的讲解。这里提前做简单介绍。
解析的过程分为分词,解析,添加三个步骤,其中第2,3步是一起进行的:

  1. 将读取到字节流分成 Token,分为 StartTagToken,EndTagToken 和 文本Token。
  2. 将 Token 解析成 DOM 节点,主要利用了栈结构的特点,第一个阶段生成的 Token 被按顺序压入栈中,并计算节点之间正确的层级关系。具体规则如下:
    • 如果压入到栈中的是 StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点
    • 如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点
    • 如果分词器解析出来的是 EndTag Token,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。

个人对 Token 解析规则的理解

如果栈中两个开始标签相邻,就代表后入栈的标签被前一个入栈的标签所包裹,反映在 DOM 树中的关系就是后入栈的开始标签生成的节点前一个入栈的标签生成的节点子节点

如果当前遍历到的标签是与当前栈顶的开始标签相匹配的结束标签,则说明该标签没有包裹其他的标签,也就代表对应生成的节点没有子节点(但可以有文本节点),此时需要将栈顶的开始标签弹出,相当于向上返回了一层。这样接下来遍历到的开始标签和刚弹出的开始标签会拥有同一个父节点,它们所生成的节点之间就是相邻关系。

当前栈顶的开始标签就可以代表下一个可能入栈的 token 在树形关系中所处的层级。如果下一个遍历到的 token 是 文本 token,因为文本节点是没有子节点的,所以只需将生成该文本 token 对应的 DOM 节点,并且插入到当前栈顶对应的 DOM 节点下即可。

样式计算

CSS 解析

CSS 对页面元素的样式起到几乎决定性的作用,然而和 HTML 一样,CSS 也是由人类可读的文本书写,要让程序便于处理,还需要对 CSS 解析,将 CSS 规则转换成便于程序读取操作的结构化的数据——styleSheets

CSSOM 树

在一些文档中还包括生成 CSSOM 树的步骤,CSSOM 树和上文的 styleSheets 的作用相同,包含了页面的所有样式,CSSOM 和 DOM树合成渲染树(对应下文的布局树)。渲染树关于功能和结构的描述和下文的布局树类似,只是在布局树的版本中,不存在 CSSOM。
CSSOM 树和 DOM 树合成渲染树的示意图
貌似这种渲染树模型已经过时了,而最新的版本采用的是布局树,但是网上的大多数资料依然用的渲染树模型,包括 Google 开发者官网的文章MDN 的文章

标准化属性值

CSS 文本有很多不同的属性值,大多数值可以标准化成同一个种类型的值,例如:2em,2cm,2px,2vh 这样的值在样式计算的过程中都需要将其转换成成统一的 px 值,以便于机器计算。

元素的样式有多个来源,并且遵循样式继承层叠规则,元素之间的样式相互影响,为了明确最终绘制在屏幕上的元素的样式,需要根据一些相关的数据计算出每个元素最终的样式。

样式的来源

  1. 行内样式:<p style="color: red"></p>
  2. 内部样式:<style type="text/css">p { color: red;}</style>
  3. 外部样式:<link href="style.css" rel="stylesheet" />

继承规则

每个 DOM 节点都包含有父节点的样式,样式计算过程中,会根据 DOM 节点的继承关系来合理计算节点样式。

层叠规则

层叠规则定义了如何合并来自多个源的属性值的算法。

CSS 是渲染阻塞的:浏览器会阻塞页面渲染直到它接收和执行了所有的 CSS。CSS 对象模型随着 CSS 的解析而被构建,但是在完成之前都不能被用来构建渲染树,因为样式将会被之后的解析所覆盖而不应该被渲染到屏幕上。

总之,样式计算阶段的目的是为了计算出 DOM 节点中每个元素的具体样式,在计算过程中需要遵守 CSS 的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。

布局

在知道对一个元素应用哪些规则之后,浏览器会计算所有元素的几何属性,并且将样式应用到每个元素上。chrome 在布局阶段需要做两件事:创建布局树和布局计算。

创建布局树

并不是所有的 DOM 节点都需要参与到布局中,那些不可见的节点,例如:应用了 display: none 规则的元素,或者是 <head></head>,等待都不需要参与布局。而布局树中也可能包括 DOM 树中不存在的节点,例如块级元素和行内元素相邻时会为行内元素创建一个块级容器,这也是包含在布局树中的。
DOM 树

布局树
为了创建布局树,浏览器会遍历 DOM 树中所有可见的节点,并将其添加到布局树中,而所有不可见的节点将被忽略。

布局计算

过程比较复杂,课程中对这个部分省略了。

相关阅读


© 2022, 分享知识和生活,记录成长与感动。