通常,我们编写好 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面,但是你知道它们是如何转化成页面的吗?
HTML、CSS 和 JavaScript
从上图可以看出,HTML 的内容是由标记和文本组成。标记也称为标签,每个标签都有它自己的语义,浏览器会根据标签的语义来正确展示 HTML 内容。比如上面的<p>标签是告诉浏览器在这里的内容需要创建一个新段落,中间的文本就是段落中需要显示的内容。
如果需要改变 HTML 的字体颜色、大小等信息,就需要用到 CSS。CSS 又称为层叠样式表,是由选择器和属性组成,比如图中的 p 选择器,它会把 HTML 里面<p>标签的内容选择出来,然后再把选择器的属性值应用到<p>标签内容上。
至于 JavaScript,使用它可以使网页的内容"动"起来,比如上图中,可以通过 JavaScript 来修改 CSS 样式值,从而达到修改文本颜色的目的。
构建 DOM 树
浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树。来看看 DOM 树的构建过程,参考下图:
为了更加直观地理解 DOM 树,在控制台里面输入document后回车,这样你就能看到一个完整的 DOM 树结构,如下图所示:
图中的 document 就是 DOM 结构,你可以看到,DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 JavaScript 来查询或修改其内容。
样式计算(Recalculate Style)
样式计算的目的是为了计算出 DOM 节点中每个元素的具体样式,这个阶段大体可分为三步来完成。
把 CSS 转换为浏览器能够理解的结构
那 CSS 样式的来源主要有:
- 通过 link 引用的外部 CSS 文件
- <style>标记内的 CSS
- 元素的 style 属性内嵌的 CSS
和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets,该结构同时具备了查询和修改功能 。
可以在 Chrome 控制台中查看其结构,只需要在控制台中输入 document.styleSheets,然后就看到如下图所示的结构:
转换样式表中的属性值,使其标准化
什么是属性值标准化,看下面这样一段 CSS 文本:
body { font-size: 2em }
p {color:blue;}
span {display: none}
div {font-weight: bold}
div p {color:green;}
div {color:red; }
可以看到上面的 CSS 文本中有很多属性值,如 2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。
计算出 DOM 树中每个节点的具体样式
接下来就需要计算 DOM 树中每个节点的样式属性了,如何计算呢?这就涉及到 CSS 的继承规则和层叠规则了。
- 首先是 CSS 继承。CSS 继承就是每个 DOM 节点都包含有父节点的样式。
为了加深对 CSS 继承的理解,打开 Chrome 的开发者工具:
- 层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位,CSS 的全称"层叠样式表"正是强调了这一点。
如果想了解每个 DOM 元素最终的计算样式,可以打开 Chrome 的开发者工具,选择第一个"element"标签,然后再选择"Computed"子标签,如下图所示:
布局阶段
现在,我们有 DOM 树和 DOM 树中元素的样式,但这还不足以显示页面,因为我们还不知道 DOM 元素的几何位置信息。那么接下来就需要计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局。这个过程包括构建布局树和布局计算两个步骤:
创建布局树
DOM 树还含有很多不可见的元素,比如 head 标签,还有使用了 display:none 属性的元素。所以在显示之前,要额外地构建一棵只包含可见元素布局树。结合下图来看看布局树的构造过程:
从上图可以看出,DOM 树中所有不可见的节点都没有包含到布局树中。
布局计算
接下来,就要计算布局树节点的坐标位置了,布局的计算过程非常复杂,暂且忽略。
分层
现在有了布局树,而且每个元素的具体位置信息都计算出来了,那么接下来是不是就要开始着手绘制页面了?
答案依然是否定的。
因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。如果你熟悉 PS,相信你会很容易理解图层的概念,正是这些图层叠加在一起构成了最终的页面图像。
从上图可以看出,渲染引擎给页面分了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面。
再来看看这些图层和布局树节点之间的关系,如图所示:
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
那么需要满足什么条件,渲染引擎才会为特定的节点创建新的图层呢?通常满足下面两点中任意一点的元素就可以被提升为单独的一个图层。
第一点,拥有层叠上下文属性的元素会被提升为单独的一层。
页面是个二维平面,但是层叠上下文能够让 HTML 元素具有三维概念,这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。结合下图来直观感受下:
从图中可以看出,明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性 。
第二点,需要剪裁(clip)的地方也会被创建为图层。
把 div 的大小限定为 200 * 200 像素,而 div 里面的文字内容比较多,文字所显示的区域肯定会超出 200 * 200 的面积,这时候就产生了剪裁。出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。参考下图:
所以说,元素有了层叠上下文的属性或者需要被剪裁,满足其中任意一点,就会被提升成为单独一层。