字节小程序
开发者社区
小程序小游戏
登录
技术淘金丨浏览器渲染流程

技术淘金丨浏览器渲染流程

872浏览作者: falafala

为什么需要了解渲染流程?了解浏览器的渲染流程,对于我们该怎么去优化页面性能有极大的帮助,深挖经验之谈背后的原理。在看完本文之后可以解答浏览器如何处理HTML、CSS、JavaScript并将它们显示在屏幕上。

前言:本文主要参考了《浏览器工作原理与实践》非常优秀,而且图片也制作精美,本文直接引用。

背景

浏览器的架构

现代浏览器的架构都是多进程架构(multi-process architecture),它也是不断在不断的解决问题当中演化而来,而多进程架构优势明显,也就被不同的浏览器厂商采用。以Chrome为例:


  • 浏览器进程(Browser Process):控制浏览器这个应用的chrome(主框架)部分,包括地址栏、书签、前进/后退按钮等,同时也会处理浏览器不可见的高权限任务,如发送网络请求、访问文件。
  • 渲染器进程(Renderer Process):控制显示网站的选项卡内的任何内容;图中渲染进程有多层,表示Chrome为每个选项卡运行多个渲染器进程。
  • 插件进程:控制网站使用的任何插件,例如 Flash。
  • GPU进程:在独立的进程中处理GPU任务。之所以放到独立的进程,是因为GPU要处理来自多个应用的请求,但要在同一个界面上绘制图形。

多进程架构优势

  1. 更强的健壮性在最简单的情况下,您可以想象每个选项卡都有自己的渲染器进程。假设您打开了 3 个选项卡,每个选项卡都由一个独立的渲染器进程运行。如果一个选项卡变得无响应,那么您可以关闭无响应的选项卡并继续前进,同时保持其他选项卡处于活动状态。如果所有选项卡都在一个进程上运行,则当一个选项卡无响应时,所有选项卡均无响应。
  1. 安全性和沙盒由于操作系统提供了一种限制进程权限的方法,因此浏览器可以对某些进程的某些功能进行沙箱处理。例如,Chrome 浏览器限制处理任意用户输入的进程(如渲染器进程)的任意文件访问。
  1. 节省更多的内存(Chrome 中的服务化)Chrome 正在经历架构更改,以将浏览器程序的每个部分作为服务运行,从而允许轻松拆分为不同的进程或聚合为一个。一般的想法是,当 Chrome 在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程以提供更高的稳定性,但如果它在资源受限的设备上,Chrome 会将服务整合到一个进程中以节省内存占用。
  1. 站点隔离:有兴趣的同学可以自行了解site-isolation

渲染流程

渲染器进程的核心工作是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页。由于渲染机制过于复杂,所以渲染模块在执行的过程中会被划分为很多子阶段,通过输入HTML经过这些子阶段最后输出像素,我们把这样一个处理流程叫做渲染流水线。

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

构建DOM树


如何生成DOM树?

在渲染引擎内部,有个叫HTML解析器(HTMLParser)的模块,负责将HTML字节流转换成DOM结构。那字节流是如何转换为DOM的呢?如下图所示:


①通过分词器将字节流转换为Token;Token分为Tag Token和文本Token。Tag Token又可分为StartTag和EndTag,对应着标签的开和闭,eg:<body>、</body>。


②将Token解析为DOM节点(图中的Node),并将DOM节点添加到DOM树中。HTML解析器维护了一个Token栈结构,用来计算节点之间的父子关系。类似于用栈来匹配带括括号的表达式"3*(7+(4÷2))",不再详细展开。


样式计算

样式计算的目的是为了计算出DOM节点中每个元素的具体样式,这一阶段大致可分三步来完成:

①把CSS转换为浏览器能够理解的结构;

渲染引擎无法直接理解CSS文件的内容,所以需要将其解析成渲染引擎能够理解的结构,即styleSheets。在浏览器的控制台输入document.styleSheets就可以查看其结构。


 CSSOM(CSS Object Model)是一组允许用JavaScript操纵CSS的API。CSSOM有两个作用,一个是提供给JavaScript操作样式表的能力;二是为布局树的合成提供基础的样式信息。

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

有一些CSS属性,像font-size: 2em、coler: blue、font-weight: bold,不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。


③计算出DOM树中每个节点的具体样式;

如何计算DOM树每个节点的属性?涉及CSS的继承规则和层叠规则。

继承规则,就是每个DOM节点都包含有父节点的样式。  

  举个🌰:

body { font-size: 20px }
p {color:blue;}
span  {display: none}
div {font-weight: bold;color:red}
div  p {color:green;}

这个样式表最终应用到DOM节点的效果如下图所示;


也可以从Chrome的开发者工具中看到如下样式的继承过程界面:


层叠规则,也是CSS的一个基本特征,它定义了如何合并来自多个源的属性值算法。CSS全称层叠样式表也强调了这点(具体的层叠规则可查看:层叠与继承,不做过多介绍)。

布局(Layout)

现在我们有DOM树和DOM树中元素的样式,但还不能显示页面,因为缺少了DOM元素的几何位置。计算出DOM树中可见元素的几何位置,这个过程叫布局。

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

  • 创建布局树(Layout Tree)

布局树的基本结构就是复制DOM树的结构,循环遍历DOM树中所有可见节点,并把这些节点加到布局树中,而不可见的节点会被忽略掉(不可见的元素例如<head>标签下的全部内容,被设为display: none的元素)。

如下图,图中的<span>标签设置了display: none,会在布局树中移除。


  • 布局计算

拥有了完整的布局树之后,就需要计算每个布局树节点的精确位置和大小,布局的计算过程非常复杂,有兴趣的同学可以查阅相关资料。

分层(Layer)

页面中有很多复杂的效果,如3D变换、页面滚动,或者使用z-index做z轴排序等,为了更加方便的实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一颗对应的图层树(Layer Tree)。类比于PS的图层概念,正是这些图层叠加在一起构成了最终的页面图像。

要想直观地理解什么是图层,你可以打开 Chrome 的“开发者工具”,选择“Layers”标签,就可以看到可视化页面的分层情况,如下图所示:


通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。那么需要满足什么条件,渲染引擎才会为特定的节点创建新的图层呢?通常满足下面两点中任意一点的元素就可以被提升为单独的一个图层。

  • 第一点,拥有层叠上下文属性的元素会被提升为单独的一层。


从图中可以看出,明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。

  • 第二点,需要剪裁(clip)的地方也会被创建为图层。

什么是剪裁?


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

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

渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域。出现剪裁情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层,如下图:


绘制(Paint)

构建完图层树之后,渲染引擎会对图层树中的每个图层进行绘制。渲染引擎是怎么实现绘制的?

其实跟我们画画一样,把图层的绘制拆分成一个个小的绘制指令,如先画个矩形、再画个边框、画个背景,这些绘制指令组成一个绘制列表。


也可以打开“开发者工具”的“Layers”标签,选择“document”层,来实际体验下绘制列表,如下图所示:


光栅化(Raster)

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。结合下图来看下渲染主线程和合成线程之间的关系:


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

通常一个页面可能很大,但用户只能看到其中的一部分,叫视口(viewport)。有些图层也可以很大,但用户通过viewport只能看到其中一部分,这种情况下就没有必要将所有的图层绘制出来了,基于这个原因,合成线程会将图层划分为图块(tile)。


合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。

渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,如下图所示:


通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。


小结,光栅化就是按照绘制列表中的指令生成图片,每一个图层都对应一张图片。

合成(Composite)与显示

在光栅化完成之后,每一个图层都对应一张图片,合成线程会将这些图片合成为“一张”图片。

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

需要重点关注的是,合成操作是在合成线程上完成的,这也就意味着在执行合成操作时,是不会影响到主线程执行的。这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因。

  • 那显示器是怎么显示图像的?

每个显示器都有固定的刷新频率,通常是 60HZ,也就是每秒更新 60 张图片,更新的图片都来自于显卡中一个叫前缓冲区的地方,显示器所做的任务很简单,就是每秒固定读取 60 次前缓冲区中的图像,并将读取的图像显示到显示器上。

  • 那么这里显卡做什么呢?

显卡的职责就是合成新的图像,并将图像保存到后缓冲区中,一旦显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样就能保证显示器能读取到最新显卡合成的图像。

通常情况下,显卡的更新频率和显示器的刷新频率是一致的,正常显示器是60帧/s(或者60FPS)。但有时候,在一些复杂的场景中,显卡处理一张图片的速度会变慢,这样就会造成视觉上的卡顿。

渲染流水线总结

好了,我们现在已经分析完了整个渲染流程,从 HTML 到 DOM、样式计算、布局、图层、绘制、光栅化、合成和显示。下面我用一张图来总结下这整个渲染流程:


含有 JavaScript 和 CSS 的页面渲染流水线


图中需要注意的点:

  1. HTML 解析器并不是等整个文档加载完成之后再解析的,而是网络进程加载了多少数据,HTML 解析器便解析多少数据。
  1. 预解析,如果遇到JavaScript文件或者CSS文件,那预解析线程会提前下载这些数据。
  1. 在解析DOM的过程中,如果遇到JavaScript脚本,那么需要先暂停DOM解析去执行JavaScript脚本,因为JavaScript有可能会修改当前状态下的DOM。
  1. 在执行JavaScript脚本之前,如果页面中有引用外部CSS或有内联的CSS,那么渲染引擎需要先把这些内容转换为CSSOM,因为JavaScript有修改CSSOM的能力,所以在执行JavaScript之前还依赖CSSOM。也就是说CSS会间接阻塞DOM的解析。

相关概念

Reflow(重排/回流)


从上图可以看出,如果你通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。

无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

Repaint(重绘)


从图中可以看出,如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。

相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。

Composite(合成)


在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。

这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。

判断CSS属性是否会触发重排/重绘/合成:https://csstriggers.com

参考资料

  1. 《浏览器工作原理与实践》
  1. Inside look at modern web browser (part 1)
  1. Inside look at modern web browser (part 3)
  1. Web Fundamentals:Rendering Performance
  1. How Browers Work,(社区中译版


最后一次编辑于 2022年05月23日
加载中