距上次的导航流程已有些日子,趁着这次休假把剩下的渲染流程也分享出来。
通过本文可以透视浏览器是如何进行渲染工作的。
众所周知,浏览器传输的相关文件包括 js文件html文件css文件。
//HTMl: 超文本 标记语言
<p>山野乔治</p>
//CSS:层叠样式表
p { color: red }
//JavaScript
p.style.color = 'gray' 通过页面修改内容
1.html的内容由标记和文本组成,标记也称为标签。每个标签都有自己特定的含义,浏览器会根据不同的语义化的标签来进行渲染不同的内容。比如上面的的<p>标签会告诉浏览器在这里需要创建一个新的段落,中间的文本就是段落要显示的内容。
2.css文件又称为层叠样式表,是由选择器和属性组成。通过css可以改变文字的大小,颜色等信息。图中的p标签,会把设定的属性值,全部应用到p标签上。
3.至于JavaScript,使用这个可以让页面的内容‘动’起来。也就是大部分的用户和浏览器的交互操作,都是通过JavaScript完成的。
搞清楚了HTML,CSS,JavaScript基本含义之后,我们就开始详细了解浏览器的渲染机制吧。
渲染模块在执行过程中会被划分为很多子阶段,输入的HTML经过这些子阶段,最后输出为了像素。我们把这样的一个处理流程就叫做渲染流水线,大致过程如下图所示。
按照渲染的时间顺序,流水线可以分为这几个子阶段:
- 构建DOM树
- 样式计算
- 布局阶段
- 分层
- 绘制
- 分块
- 光栅化
- 合成
每个阶段的过程中,我们应该重点关注:1,开始每个子阶段都有其输入的内容;2,然后每个子阶段有其处理过程;3,最终子阶段会生成其输出内容。也就是:输入-》加工-》输出
构建DOM树
为什么要构建DOM树呢?
我们浏览器是无法直接理解和使用HTML的(HTML标记语言是为了让开发人员进行编程的语言)所以浏览器需要将转化为浏览器可以理解的结构,那就是---DOM树。
了解过数据结构的开发人员应该都清楚什么是树结构。有一父节点可以有很多个子节点,但一个子节点不能被不同的父节点共享。在我们浏览器的渲染机制中,就频繁的使用到了树结构。
那么DOM树是如何进行构建并进行渲染的呢?
在构建DOM树的时候,输入内容就是一个简单的HTML文件,然后由HTML解析器进行解析,最终输出一个树状结构的DOM。
为了能直观的了解什么是DOM结构,可以在控制台上输入document进行查看完整的一个DOM结构。DOM和HTML内容几乎一摸一样的,但是和HTML不同的是,DOM是保存在内存中的树状结构,可以通过JavaScript来查询或修改其内容。如何通过JavaScript来修改DOM的内容呢?控制台输入以下内容
document.getElementsByTagName('p')[0].innerText = 'black'
这个代码作用就是把第一个P标签的文本内容转换为black。当执行这行代码的时候,DOM的一个p节点的内容成功被修改,同时页面的内容也会被修改。
这就是生成DOM树的过程,此刻DOM节点的样式我们依然不知道,要让DOM拥有正确的样式,就需要进行下一个渲染子进程了。
样式计算(Recalculate Style)
样式计算的目的是为了计算DOM节点中每个元素的具体样式,这个子进程大概需要分为三步来完成。
1.把CSS转化为浏览器能够理解的结构
那么CSS样式的来源主要有哪些呢?
通过link引用的外部CSS文件
<style>标记内的CSS
元素的style属性内嵌的CSS
和第一个构建DOM树一样,浏览器也是不能直接理解纯文本的CSS样式,所以当渲染引擎就收到css文件的时候,首先会进行一个转化操作,将css文本转化为浏览器可以理解的结构---styleSheets
同样,想要更要直观的查看styleSheets的结构,我们可以在控制台输入document.styleSheets,就能看到对应的css结构。
可以看出,这个样式表包含了很多样式,已经把那三种来源的样式都包含进去了。当然样式表的具体结构在这里不赘述,只介绍渲染引擎会把获取到的CSS文件全部转换为styleSheets结构中的数据,并且该结构同时具备了查询和修改功能,为后面的样式操作提供了基础。到此样式表转化完毕。
2.转换样式表中的属性值,使其标准化
我们已经把现有的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。这些类型数值都不容易被渲染引擎所理解,所以需要将所有值转换为渲染引擎所能理解的标准化的计算值,这个过程就是属性值标准化。
那么标准化后的属性值是什么呢?
body { font-size: 32px }
p { color: rgb(0,0,255) }
span { display: none }
div { font-weight: 700 }
div p { color: rgb(0,128,0) }
div { color: rgb(255,0,0) )
3.计算出DOM树中的每个节点的具体样式
当属性值已经被标准化后,接下来我们就要计算DOM树中的每个节点的样式属性了,如何计算呢?
这就涉及到CSS的继承规则和层叠规则了。
首先是CSS继承。CSS继承就是每个DOM节点都包含有父节点的样式 这么说可能有点抽象,实例是如何应用的呢?
body { font-size: 20px }
p {color: blue}
span { display: none }
div { font-weight: bold; color: red }
div p { color: green }
那么如何继承呢?如下图所示
所有的子节点都继承了父节点的样式。比如body节点的font-size属性是20,那么body节点下面的所有节点的font-size都等于20。
样式计算过程中第二个规则就是样式层叠。层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在css中处于核心地位,css的全称是“层叠样式表”正是强调了这一点。关于层叠的具体规则这里不做介绍。
总之,样式计算阶段的目的救赎为了计算出DOM节点中每个元素的具体样式,在计算过程中需要遵守CSS的继承和层叠两个规则。这个阶段最中输出的内容就是每个DOM节点的样式,并被保存在ComputerStyle的结构内。
如果想要了解每个DOM元素最终的计算样式,可以打开Chrome的“开发者工具,选择一个element标签,然后再选择“Computer”子标签。
在进行计算之后,我们就进行布局阶段。
布局阶段
我们有DOM树和DOM树中元素的样式之后,我们还不足以显示页面,因为还不知道DOM元素的几何位置信息。那么接下来就需要计算出DOM树中可见元素的几何位置,我们把这个计算过程叫做布局
Chrome在布局阶段需要完成两个任务:创建布局树和布局计算。
1.创建布局树
你可能注意到了DOM树还含有很多不可见的元素,比如head标签,还有使用了display:none属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。
我们结合下图来看看布局树的构造过程:
从上图可以看出,DOM树中所有不可见的节点都没有包含到布局树中。
为了构建布局树,浏览器大体上完成了下面这些工作:
- 遍历DOM树中的所有可见节点,并把这些节点加到布局树中;
- 而不可见的节点会被布局树忽略掉,如head标签下面的全部内容,再比如body.p.span这个元素,因为它的属性包含display:none,所以这个元素也没有被包进布局树。
2.布局计算
现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了。布局的计算过程非常复杂,等到后续详解。
在执行布局操作的时候,会把布局计算的结果重新写会布局树中,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段并没有清晰地将输入内容和输出内容区分开。对于这个问题,Chrome团队正在重构布局代码,下一代布局系统叫做LayoutNG,试图分离输入和输出,从而让布局算法更加简单。
综上本问介绍了渲染进程的三个子进程:DOM生成,样式计算和布局。
总结:
- 浏览器不能直接理解HTML文件,通过转化器将转化为浏览器能理解的DOM树结构。
- 生成DOM树之后,还需要根据CSS样式表,来计算出DOM树所有节点的样式。
- 最后计算DOM元素的布局信息,使其保存在布局树中(输入DOM树,计算布局信息后,输出DOM树)。
子过程比较多,小伙伴迫切叫我健身。渲染进程那就先到这里了,剩下的进程我们下节再叙~