浏览器工作原理2(渲染阶段)

616 阅读5分钟

总览

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。

DOM 树构建

输入HTML,输出DOM

dom.png

样式计算

CSS 计算是把 CSS 规则应用到 DOM 树上,为 DOM 结构添加显示相关属性的过程。这个阶段大体可分为三步来完成

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

    当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets。

    为了加深理解,你可以在 Chrome 控制台中查看其结构,只需要在控制台中输入 document.styleSheets

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

    简单理解不同单位,不同标准的归一化操作(rem,blue..)

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

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

布局阶段

DOMTree + CSSOM = LayoutTree

layoutTree.png

遍历 DOM 树中的所有可见节点,并把这些节点加到布局中;

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

分层

为何分层 ?

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree) 浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面.

layerTree.png

什么情况下会生成新图层

  1. 拥有层叠上下文属性的元素会被提升为单独的一层。

    position:fixed/z-index:10/filter:blur(5px)/opacity:0.5

  2. 需要剪裁(clip)的地方也会被创建为图层。

<style>
   div {
         width: 200;
         height: 200;
         overflow:auto;
         background: gray;
     } 
</style>
<body>
 <div >
     <p> 所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p>
     <p> 从上图我们可以看到,document 层上有 AB 层,而 B 层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p>
     <p> 图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p> 
 </div>
</body>

在这里我们把 div 的大小限定为 200 * 200 像素,而 div 里面的文字内容比较多,文字所显示的区域肯定会超出 200 * 200 的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域

图层绘制

渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表

paint.png

栅格化(raster)操作

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

raster.png

当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程,那么接下来合成线程是怎么工作的呢?

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

为了节省开销,通常按快绘制,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512

然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。

所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示

rasterTask.png

渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。

合成和显示

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

浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。

总结

渲染进程将 HTML 内容转换为能够读懂的DOM 树结构。

渲染引擎将 CSS 样式表转化为浏览器可以理解的styleSheets,计算出 DOM 节点的样式。 创建布局树,并计算元素的布局信息。

对布局树进行分层,并生成分层树。

为每个图层生成绘制列表,并将其提交到合成线程。

合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

合成线程发送绘制图块命令DrawQuad给浏览器进程。

浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

summary.png

repaint VS reflow

repaint 发生在 Style阶段,需要浏览器会触发重新布局,解析之后的一系列子阶段

reflow 发生在layer或者layout 阶段,不需要计算布局