跟面试官说我是这样理解浏览器渲染流程的(上)

1,560 阅读9分钟

面试官:浏览器的渲染流程?

你可能会这样回答:

  1. 首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。
  2. 然后对 CSS 进行解析,生成 CSSOM 规则树。
  3. 根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。
  4. 当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
  5. 布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件。

相信大家对于这个问题的八股文答案都倒背如流了,接下来两篇文章会带大家深入了解浏览器渲染流程的各个节点的实现。

通常我们写好的 HTML、CSS、JavaScript 等文件,经过浏览器的一系列处理漂亮的图案就显示在了浏览器上了。大多数人只知道大概的粗略流程,但是背后的具体原理,估计很多人答不上来。

image.png

流程渲染示意图

上图展示了左边输入 HTML、CSS、JavaScript 数据,经过中间渲染流程的处理最终输出为屏幕上的像素。

为了更好的理解上下文先来付一下我们的前端三要素:

前端三要素:

image.png

HTML

HTML(超文本标记语言——HyperText Markup Language)是构成 Web 世界的一砖一瓦。它定义了网页内容的含义和结构。比如上面的 <p> 标签是告诉浏览器在这里的内容需要创建一个新段落,中间的文本就是段落中需要显示的内容。

CSS

层叠样式表(Cascading Style Sheets,缩写为 CSS)是一种样式表语言,用来描述 HTML 或 XML(包括如 SVGMathML 或 XHTML 之类的 XML 分支语言)文档的呈现方式。CSS 描述了在屏幕、纸质、音频等其他媒体上的元素应该如何被渲染的问题。

比如图中的p选择器,它会把HTML里面 <p> 标签的内容选择出来,然后再把选择器的属性值应用到 <p> 标签内容上。选择器里面有个 color 属性,它的值是 red ,这是告诉渲染引擎把 <p> 标签的内容显示为红色。

Javasctipt

JavaScript(简称为JS),使用它可以使网页的内容“动”起来,比如上图中,可以通过JavaScript来修改CSS样式值,从而达到修改文本颜色的目的。

搞清楚HTML、CSS和JavaScript的含义后,那么接下来我们就正式开始分析渲染模块了。

由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的HTML经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线,其大致流程如下图所示:

image.png

渲染流水线示意图

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建DOM树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。接下来,在介绍每个阶段的过程中,你应该重点关注以下三点内容:

  • 开始每个子阶段都有其输入的内容;
  • 然后每个子阶段有其处理过程;
  • 最终每个子阶段会生成输出内容。

理解了这三部分内容,能让你更加清晰地理解每个子阶段。

DOM树的构建

为什么有 DOM树的存在?因为浏览器无法直接识别 HTML,因此要把 HTML 转换为浏览器能识别的 DOM树

先简单的了解一下数据结构中的-树结构

image.png

tree

树这种结构非常像我们现实生活中的“树”,其中每个点我们称为节点,相连的节点称为父子节点。树结构在浏览器中的应用还是比较多的,比如下面我们要介绍的渲染流程,就在频繁地使用树结构。

接下来咱们还是言归正传,来看看DOM树的构建过程,你可以参考下图:

image.png

构建DOM树

上图中构建DOM树的输入内容是一个很简单的HTML片段,然后经过HTML解析器解析,最终生成DOM树

为了能直观的了解DOM树,可以 F12 打开 Chrome 的控制台,选择 "Console", 然后在控制台里面输入 "document" 后回车,这样就能看到一个完整的 DOM树 结构,如下图所示:

image.png

DOM可视化

在输入 "document" 可以看到输出的 DOM 结构和 HTML 内容几乎一样,但是不同的是 DOM是保存在内存中的树状结构,可以通过 JavaScript 查询或修改内容。

document.getElementByTagName("p")[0].innerText = "测试"

image.png

通过JS修改DOM

通过上图可以了解到执行了修改 p 标签的 JS 代码后,DOM的第一个 p 节点内容成功被修改同时页面内容页变了,现在已经知道 DOM树了,接下来要想 DOM树是怎样显示成想要的样子就涉及到样式计算了。

样式计算

样式计算的目的是为了计算出DOM节点中每个元素的具体样式,这个阶段大体可分为三步来完成。

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

CSS样式主要有三种:

  • 通过link引用的外部CSS文件
  • <style>标记内的 CSS
  • 内联CSS

image.png

CSS引入的三种方式

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

为了加深理解,你可以在 Chrome 控制台中查看其结构,只需要在控制台中输入document.styleSheets,然后就看到如下图所示的结构:

image.png

styleSheets

染引擎会把获取到的CSS文本全部转换为styleSheets结构中的数据,并且该结构同时具备了查询和修改功能。

2.标准化样式表中的属性值

CSS 解析器将 CSS 文件转换为 styleSheets 后就需要对数据进行标准化处理,将属性值转换为渲染引擎容易理解的,标准化的计算值,这个过程为标准化。

首先我们先看一下正常开发写的CSS代码:

body {
    font-size: 2em
}

p {
    color: blue;
}

div {
    font-weight: bold;
}

div p {
    color: green;
}

可以看到开发中会用到很多属性值,接下来看一下转换后的属性值:

image.png

标准化转变

从图中可以看到,2em被解析成了32px,red被解析成了rgb(255,0,0),bold被解析成了700……

3.计算每个 DOM 节点的样式

计算 DOM 节点的样式涉及 CSS 继承和层叠规则。

首先是CSS继承。CSS继承就是每个DOM节点都包含有父节点的样式。这么说可能有点抽象,我们可以结合具体例子,看下面这样一张样式表是如何应用到DOM节点上的。

body { font-size: 20px }
p {color:blue;}
span  {display: none}
div {font-weight: bold;color:red}
div  p {color:green;}

通过继承最终展现在 DOM 节点的样式就是如下图的样子:

image.png

计算后的DOM节点

为了加深对CSS继承的理解,可以打开 Chrome 的“开发者工具”,选择第一个 element 标签,再选择 style 子标签,你会看到如下界面:

image.png

继承

样式计算过程中的第二个规则是样式层叠。层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在CSS处于核心地位,CSS的全称“层叠样式表”正是强调了这一点

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

如果你想了解每个 DOM 元素最终的计算样式,可以打开 Chrome 的“开发者工具”,选择第一个 “element” 标签,然后再选择 “Computed” 子标签,如下图所示:

image.png

层叠

布局阶段

现在有DOM树和DOM树中元素的样式,但这还不足以显示页面,因为还不知道DOM元素的几何位置信息。那么接下来就需要计算出DOM树中可见元素的几何位置,我们把这个计算过程叫做布局。

Chrome在布局阶段需要完成两个任务:

  • 创建布局树
  • 布局计算

1. 创建布局树

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

下图为构建布局树的过程:

image.png

为了构建布局树,浏览器大体上完成了下面这些工作:

  • 遍历DOM树中的所有可见节点,并把这些节点加到布局树中;
  • 而不可见的节点会被布局树忽略掉,如head标签下面的全部内容,再比如body.p.span这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。

2. 布局计算

布局计算比较复杂详细内容请看下章分解。

小结

image.png

本篇介绍了渲染流程的前三个阶段:DOM生成、样式计算和布局。要点可大致总结为如下:

  • 浏览器不能直接理解HTML数据,所以第一步需要将其转换为浏览器能够理解的DOM树结构;
  • 生成DOM树后,还需要根据CSS样式表,来计算出DOM树所有节点的样式;
  • 最后计算DOM元素的布局信息,使其都保存在布局树中。