渲染流程

HTML, CSS, JavaScript是如何变成页面的?

由于渲染过程过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的HTML,CSS,JavaScript经过这些子阶段,最后输出像素。这样的一个处理流程称为渲染流水线

按照渲染的时间顺序划分,可将渲染流水线划分为:

  1. 构建DOM树(DOM)
  2. 样式计算(Style)
  3. 布局阶段(Layout)
  4. 分层(Layer)
  5. 绘制(Paint)
  6. 分块(tiles)
  7. 光栅化(raster)
  8. 合成和显示(display)
image-20201102214027828

一、构建DOM树(DOM)

浏览器是无法直接理解和使用HTML的,所以需要将HTML转换为浏览器能理解的结构——DOM树

1. Input

HTML文件

2. Handle

HTML解析器解析

image-20201102112721683

3. Output

DOM树(DOM树是保存在内存中的树状结构,document)

二、样式计算(Recalculate Style)

同样的,浏览器是无法理解纯文本的CSS样式的,所以渲染引擎会把CSS转化为可以理解的styleSheets,并计算出DOM节点中每个元素的具体样式。

计算的目的是为了计算出DOM节点中每个元素的具体样式。

1. Input

  1. <link ref="stylesheet" href="">
  2. <style></style>
  3. style=""

2. Handle

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

    浏览器会把CSS转换为浏览器能够理解的结构——styleSheets(document.styleSheets)

    注:也有人将styleSheet称之为CSSOM。

  2. 转换样式表中的属性值,使其标准化

    将所有属性值转为渲染引擎容易理解的、标准化的计算值。如颜色值会转为rgb或rgba形式

    image-20201102111337794
  3. 计算出DOM树中每个节点的具体样式:

    根据CSS的继承规则层叠规则计算出DOM树中每个节点的样式属性。

    image-20201102220728216

3. Output

每个DOM节点的样式,保存在ComputedStyle中

image-20201102112825987

三、布局阶段(Layout)

计算出DOM树中可⻅元素的⼏何位置信息。

Chrome在布局阶段需要完成两个任务: 创建布局树布局计算

1. 创建布局树

  • Input:DOM树,ComputedStyle

  • Handle:

    遍历DOM树中的所有可⻅节点, 并把这些节点加到布局树中,⽽不可⻅的节点(如head标签的节点,display:none)会被布局树忽略掉

    image-20201102113838882
  • Output:⼀棵只包含可⻅元素布局树(LayoutTree)

2. 布局计算

  • Input:布局树(LayoutTree)

  • Handle:计算布局树节点的坐标位置,并重新写回布局树

  • Output:布局树(LayoutTree)

布局阶段有⼀个不合理的地⽅:在执⾏布局操作的时候, 会把布局计算的结果重新写回布局树中, 所以布局树既是输⼊内容也是输出内容,并没有清晰地将输⼊内容和输出内容区分开来。针对这个问题, Chrome团队正在重构布局代码, 下⼀代布局系统叫LayoutNG, 试图更清晰地分离输⼊和输出, 从⽽让新设计的布局算法更加简单。

注:也有人将LayoutTree称之为RenderTree,但两者之间还是有些差别的。

四、分层(Layer)

⻚⾯中有很多复杂的效果,如⼀些复杂的3D变换、⻚⾯滚动,或者使⽤z-index做z轴排序等。

为了更加⽅便地实现这些效果,渲染引擎还需要为特定的节点⽣成专⽤的图层,并⽣成⼀棵对应的图层树(LayerTree)

Chrome的“开发者⼯具”,选择“Layers”标签,就可以可视化⻚⾯的分层情况。

1. Input

布局树(LayoutTree)

2. Handle

为特定的节点生成专用的图层。

并不是布局树的每个节点都是⼀个图层的,如果⼀个节点没有对应的层,那么这个节点就从属于⽗节点的图层。最终每⼀个节点都会直接或者间接地从属于某⼀个层。

image-20201102113104272

当满足以下任意一条特性时,就会为该节点生成专用的图层:

  • 拥有层叠上下⽂属性的元素会被提升为单独的⼀层。如position的fixed,absolute以及z-index等。

  • 需要剪裁(clip)的地⽅也会被创建为图层,如文字内容超出了限定区域就会创建图层,同时滚动条也会创建一个图层。

    1
    2
    3
    4
    <div style="width: 200px;height: 200px;overflow: auto;background: gray;">
    <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Asperiores quia nemo assumenda, repellendus voluptatibus suscipit recusandae corporis odit facilis atque porro aliquid, ea iste molestiae magnam unde, ut nostrum molestias!
    </p>
    </div>

3. Output

一颗对应的图层树(LayerTree)

五、绘制(Paint)

渲染引擎会对图层树(LayerTree)中的每个图层⽣成绘制列表,并将其提交到合成线程

绘制列表只是⽤来记录绘制顺序和绘制指令的列表,⽽实际上绘制操作是由渲染引擎中的合成线程来完成的。

同样的,Chrome的“开发者⼯具”,选择“Layers”标签,可以查看绘制指令。

1. Input

图层树(LayerTree)

2. Handle

  1. 把一个图层的绘制拆成很多小的绘制指令
  2. 将这些绘制指令按顺序组成一个待绘制列表
image-20201102115635248

3. Output

生成待绘制列表,并将待绘制列表提交给合成线程

六、分块(tiles)

当绘制列表准备好之后,渲染进程的主线程会把该绘制列表提交(commit)给渲染进程中的合成线程

通常一个页面可能很大,但是用户通过屏幕只能看到其中的一小块,我们把通过屏幕可以看到的这个部分叫做视口(物理视口)

在有些情况下,有的图层很⼤,⽐如有的⻚⾯要滚动好久才能滚动到底部,但是通过视⼝,⽤⼾只能看到⻚⾯的很⼩⼀部分,所以在这种情况下,要绘制出所有图层内容的话,就会产⽣太⼤的开销,⽽且也没有必要。

基于这个原因,合成线程会将图层划分为图块(tile)。通常这些图块的⼤⼩是256x256或者512x512。

1. Input

待绘制列表(一个绘制列表就是一个layer)

2. Handle

将图层划分成图块

image-20201102211027088

3. Output

图层对应的图块

七、光栅化(raster)

所谓栅格化就是指将图块转换为位图。所以栅格化执行的最小单位就是图块。

渲染进程维护了一个栅格化的线程池,所有的图块栅格化操作都是在线程池内执行的。

光栅化就是指在栅格化的过程中,会使用GPU来加速生成位图,所以也可以称之为快速栅格化或GPU栅格化。

1. Input

图层对应的图块(tiles)

2. Handle

  • 栅格化线程池内的栅格化线程会按照视口附近的图块优先生成位图
  • 栅格化过程都会使⽤GPU来加速⽣成位图,⽣成的位图被保存在GPU内存中。(跨进程操作)
image-20201102213246253

3. Output

图层的图块对应的位图,并保存在GPU内存中。

一旦所有图块都被光栅化,合成线程会生成一个绘制图块的命令(DrawQuad命令),然后将该命令提交给浏览器进程

八、合成和显示

根据DrawQuad消息⽣成⻚⾯,并显⽰到显⽰器上。

1. Input

合成线程发过来的DrawQuad命令

2. Handle

浏览器进程里面有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令。

然后根据DrawQuad命令,将⻚⾯内容绘制到内存中,最后再将内存中的页面显⽰在屏幕上。

3. Output

显示的页面

九、相关概念

1. 重排:更新了元素的几何属性

修改了元素的几何位置属性,例如width,height等,那么浏览器会触发重新布局(Layout),然后按照渲染流水线执行后序的阶段,这个过程就是重排。

image-20201102213125578

重排需要更新完整的渲染流⽔线,所以开销也是最⼤的。

为什么重排会更新完成的渲染流水线?

因为修改了元素的几何属性,那么需要重新的样式计算(Recaculate Style),然后重新布局,分层绘制等,也就是重新开始更新渲染流水线。

2. 重绘:更新了元素的绘制属性

修改了元素的绘制属性,如background, color等,那么浏览器会触发重新绘制(Paint),也就是跳过Layout,Layer阶段,直接进入绘制阶段(Paint),然后按照渲染流水线执行后序的阶段,这个过程就是重绘。

image-20201102220341363

重绘省去了布局(Layout)和分层阶段(Layer),所以执⾏效率会⽐重排操作要⾼⼀些

为什么重绘会省去布局和分层阶段?

因为修改了元素的背景颜色等属性,那么需要重新的样式计算(Recaculte Style),然而元素的几何位置等属性并没有变,同时Layout和layer阶段是依赖于元素的几何位置信息的,所以完全可以跳过Layout和Layer阶段,直接进入Paint阶段,然后执行后序流程。

3. 合成:直接合成

修改了既不要重新布局也不要重新绘制的属性,会跳过Layout,Layer,Paint阶段,直接在渲染进程的非主线程上执行合成的操作,这个过程就是直接合成。

image-20201102220552144

在⾮主线程上合成,并没有占⽤主线程的资源,另外也避开了布局和绘制两个⼦阶段,所以相对于重绘和重排,合成能⼤⼤提升绘制效率。

参考链接