浏览器从输入地址到显示页面的过程,这是面试特别喜欢问的一道题,也是基础的一个概念。到现在,网上能找到的也都是很老的那一套,早之前我的认知也是那样:
DOM树、CSSOM树、render树、布局、绘制、重排、重绘。

最近在学习李兵老是的浏览器工作原理与实践,才知道现在的渲染过程早已经不是这样了,很多概念都是刚知道。自己总结了一下过程和一些概念。

1、构建DOM树:

第一步构建DOM树都是一样的,DMO是document object module,是保存在内存中的树结构,可以通过js查询或修改其内容。浏览器打印document就是这个DOM树。跟浏览器的elements里面是一样的。

helm template 渲染模板的时候如何渲染指定的文件_css

输入内容是HTML文件,通过HTML解析器解析,最终生成DOM树

2、样式计算:
以前都说是CSSOM,也就是css object module,保存在内存中用来操作css的对象,好像源码中没有这个概念,也确实没有这个对象。样式计算的过程分为三步:

第一步是转换成浏览器能够理解的结构(styleSheets):样式的来源有三种,link引入、style标签内、行内样式,第一步就是把这些整合变成样式表,可以通过document.styleSheets查看。

helm template 渲染模板的时候如何渲染指定的文件_css_02

第二步是把属性值进行标准化:我们设置样式为rem、bold、red等,最终会被解析成px、700、rgb(255, 0, 0),这就是属性值标准化。

第三步是计算出DOM树中每个节点的具体样式:css有继承规则和层叠规则。继承规则很简单,我父元素设置了字体20px,子元素如果没有另外加样式就都是20px,浏览器也有默认的,所以我们会有reset.css。层叠规则更简单,css本来也是层叠样式表的缩写,定义了如何合并多个来源的属性值算法,我的理解就是权重。查看样式styles可以看见继承和层叠,还有来源,user agent stylesheet就是浏览器默认提供的默认样式。

helm template 渲染模板的时候如何渲染指定的文件_css_03


样式计算最终输出的是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内(computed就是最终结果)。

styles可以查看样式继承和层叠规则,computed是最终的计算结果。
输入内容是css的来源,通过渲染引擎输出styleSheets。

3、布局
以前说的render树是很早的时候,说是16年以前的东西,代码早就重构,现在是LayoutTree,也就是布局树。布局是计算出DOM树中可见元素的几何位置。DOM树有些是不可见的元素,设置了display:none属性的元素、head标签、link标签等,所以显示出页面之前需要额外的构建只包含可见元素的布局树。
执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段并没有清晰地将输入内容和输出内容区分开来。针对这个问题,Chrome 团队正在重构布局代码,下一代布局系统叫 LayoutNG,试图更清晰地分离输入和输出,从而让新设计的布局算法更加简单。

4、分层

页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。

试了一下,fixed定位和body出现滚动条会出现分层,其他的不知道周末出现,可以通过chrome的layers查看:

helm template 渲染模板的时候如何渲染指定的文件_图层_04


helm template 渲染模板的时候如何渲染指定的文件_css_05

输入内容是特点的节点,渲染引擎会把这些节点生成专用的图层,生成图层树。

5、图层绘制

完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制。每一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。在图层绘制阶段,输出的内容就是这些待绘制列表。chrome的Layers可以看绘制列表。还可以看每一步绘制的过程。

helm template 渲染模板的时候如何渲染指定的文件_css_06


helm template 渲染模板的时候如何渲染指定的文件_css_06

输入内容是图层树,通过渲染引擎输出绘制列表。

6、生成图块
绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。当图层的绘制列表准备好之后,主线程会把该绘制列表提交给合成线程。页面内容可能会很大,可视窗口是固定的,如果一次性绘制所有图层,开销太大,所以合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512,然后合成线程会按照视口附近的图块来优先生成位图。

7、栅格化
生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

8、合成和显示
一旦所有图块都被栅格化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

9、重排、重绘
改变几何属性会重排,会从布局阶段开始执行,重排一定会重绘,就是改变宽高、位置等。重绘是改变颜色等,布局和分层不会重新执行。使用transform能跳过前面的阶段,直接进入合成阶段。

重新认识了一下渲染的过程,中间应该还是有很多的认知不足,现阶段也只能到这种程度了。