浏览器渲染流程
首先看一张图片,取自w3c官方 Navigation Timing
这张图片展示了向浏览器输入url之后的流程:
- 准备工作:
- prompt for unload:准备释放上一个页面的资源。
- redirect:重定向。因为浏览器是有缓存的,所以需要预取资源,缓存的本质也是重定向。在预取资源的过程中再去unload,也就是释放上一个页面的资源。
- App cache:如果有缓存就去拿缓存,然后直接走到processing,没有缓存就进入下一步(这里指的是强制缓存,如果是协商缓存,还是需要网络通信的,也会进入下一步,关于缓存相关内容可看http协议)。
- DNS:dns解析。
- TCP: 做完dns解析之后,建立tcp链接,tcp连接之后才能请求。
- 和服务器通信的阶段:
- Request: 浏览器向服务器发送请求。
- Response: 服务器向浏览器返回相应。
- 浏览器的渲染工作:
- processing:处理返回的响应。
- onLoad:展示页面。
本文主要分析第三步
渲染引擎结构与工作流程
以html/css/js等文件作为输入,然后解析,构建,渲染布局,绘制,最后以可视化内容作为输出。流程如下图:
主要步骤:
解析HTML生成DOM树。
解析CSS制定样式规则,生成CSSOM渲染规则树。
将DOM树与CSSOM规则树合并在一起生成渲染树。
遍历渲染树开始布局,计算每个节点的位置信息。
将渲染树每个节点绘制到屏幕。
构建DOM树
第一步是处理HTML标记并构造DOM树。HTML标签包括开始和结束标记,以及属性名和值。 如果文档格式良好,则解析它会简单而快速。解析器将标记化的输入解析到文档中,构建文档树。
DOM树描述了文档的内容。元素是第一个标签也是文档树的根节点。树反映了不同标记之间的关系和层次结构。嵌套在其他标记中的标记是子节点。DOM节点的数量越多,构建DOM树所需的时间就越长。
当解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。当遇到一个CSS文件时,解析也可以继续进行,但是对于script标签(特别是没有 async 或者 defer 属性)会阻塞渲染并停止HTML的解析。尽管浏览器的预加载扫描器加速了这个过程,但过多的脚本仍然是一个重要的瓶颈。
构建CSSOM树
第二步是处理CSS并构建CSSOM树。CSS对象模型和DOM是相似的。DOM和CSSOM是两棵树. 它们是独立的数据结构。浏览器将CSS规则转换为可以理解和使用的样式映射。浏览器遍历CSS中的每个规则集,根据CSS选择器创建具有父、子和兄弟关系的节点树。
与HTML一样,浏览器需要将接收到的CSS规则转换为可以使用的内容。因此,它重复了HTML到对象的过程,但对于CSS。
CSSOM树包括来自用户代理样式表的样式。浏览器从适用于节点的最通用规则开始,并通过应用更具体的规则递归地优化计算的样式。换句话说,它级联属性值。
构建CSSOM非常非常快,并且在当前的开发工具中没有以独特的颜色显示。相反,开发人员工具中的“重新计算样式”显示解析CSS、构造CSSOM树和递归计算计算样式所需的总时间。在web性能优化方面,它是可轻易实现的,因为创建CSSOM的总时间通常小于一次DNS查找所需的时间。
合成渲染树
第三步是将DOM和CSSOM组合成一个Render树,计算样式树或渲染树从DOM树的根开始构建,遍历每个可见节点。
像head和它的子节点以及任何具有display: none样式的结点,例如script { display: none; }(在user agent stylesheets可以看到这个样式)这些标签将不会显示,也就是它们不会出现在Render树上。具有visibility: hidden的节点会出现在Render树上,因为它们会占用空间。由于我们没有给出任何指令来覆盖用户代理默认值,因此上面代码示例中的script节点将不会包含在Render树中。
每个可见节点都应用了其CSSOM规则。Render树保存所有具有内容和计算样式的可见节点——将所有相关样式匹配到DOM树中的每个可见节点,并根据CSS级联确定每个节点的计算样式。
布局
第四步是在渲染树上运行布局以计算每个节点的几何体。布局是确定呈现树中所有节点的宽度、高度和位置,以及确定页面上每个对象的大小和位置的过程。回流是对页面的任何部分或整个文档的任何后续大小和位置的确定。
构建渲染树后,开始布局。渲染树标识显示哪些节点(即使不可见)及其计算样式,但不标识每个节点的尺寸或位置。为了确定每个对象的确切大小和位置,浏览器从渲染树的根开始遍历它。
在网页上,大多数东西都是一个盒子。不同的设备和不同的桌面意味着无限数量的不同的视区大小。在此阶段,考虑到视区大小,浏览器将确定屏幕上所有不同框的尺寸。以视区的大小为基础,布局通常从body开始,用每个元素的框模型属性排列所有body的子孙元素的尺寸,为不知道其尺寸的替换元素(例如图像)提供占位符空间。
第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。在我们的示例中,假设初始布局发生在返回图像之前。由于我们没有声明图像的大小,因此一旦知道图像大小,就会有回流。
绘制
最后一步是将各个节点绘制到屏幕上,第一次出现的节点称为first meaningful paint。在绘制或光栅化阶段,浏览器将在布局阶段计算的每个框转换为屏幕上的实际像素。绘画包括将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。浏览器需要非常快地完成这项工作。
为了确保平滑滚动和动画,占据主线程的所有内容,包括计算样式,以及回流和绘制,必须让浏览器在16.67毫秒内完成。在2048x 1536,iPad有超过314.5万像素将被绘制到屏幕上。那是很多像素需要快速绘制。为了确保重绘的速度比初始绘制的速度更快,屏幕上的绘图通常被分解成数层。如果发生这种情况,则需要进行合成。
绘制可以将布局树中的元素分解为多个层。将内容提升到GPU上的层(而不是CPU上的主线程)可以提高绘制和重新绘制性能。有一些特定的属性和元素可以实例化一个层,包括video和canvas,任何CSS属性为opacity、3D转换、will-change的元素,还有一些其他元素。这些节点将与子节点一起绘制到它们自己的层上,除非子节点由于上述一个(或多个)原因需要自己的层。
层确实可以提高性能,但是它以内存管理为代价,因此不应作为web性能优化策略的一部分过度使用。
- 本文作者:Ezreal
- 本文链接:https://ezreal09.github.io/2020/11/21/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E6%B5%81%E7%A8%8B/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!

