浏览器在导航结束后,是怎么将HTML CSS Javascript 代码转换成可以和用户交互的界面的呢?
本文主要讲解的是谷歌浏览器的渲染流程
普通浏览器的页面渲染流程图如下,具体分析请看《页面的渲染流程》
|
渲染进程最重要的任务就是将 HTML CSS Javascript 代码转换成可以和用户交互的界面。
在多进程浏览器中,每打开一个新页面就会创建一个进程,当然也会有些优化策略,如果同时打开多个空白页,会将多个空白页合并为一个进程。
谷歌浏览器自输入url到页面展示的完整示意图:
谷歌浏览器官网提供的渲染进程里的线程图:了解更多👉《剖析浏览器中的进程与线程》
- 主线程 Main thread
主线程中即可以执行 JS ,也可以执行渲染工作,还可以执行其他工作,但是由于是主线程是只是一个线程,所以各个工作不能同时执行,只能按照一定的先后次序执行。 - 工作线程 Worker thread
由 Worker 或 Service Worker 注册的 javascript 代码会有单独的 Worker 线程处理,独立于主线程。 - 合成线程 Compositor thread
页面平滑层次展示 - 光栅线程 Raster thread
页面快速呈现
网页选项卡内部的逻辑,都由渲染进程处理。在渲染进程中,主线程处理了绝大部分的网页代码。由 Worker 或 Service Worker 注册的 javascript 代码会有单独的 Worker 线程处理。Compositor (合成器)和 Raster (光栅)线程确保了页面快速平滑的呈现。
|
下面是目前谷歌浏览器最新构架完整的渲染流水线图
除了display
阶段是在浏览器进程进行,其他阶段都在渲染进程中进行。
结合上图,一个完整的渲染流程大致可总结为如下:
- 渲染进程将 HTML 内容转换为浏览器能够理解的 DOM 对象。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块。
- 在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
1.构建DOM |
当渲染进程接收到导航的确认信息,开始接受 HTML 数据,主线程中的HTML解释器会将文本字符串解析并输出HTML DOM。浏览器能理解的是DOM对象。
加载子资源 |
一个网页生成时通常会使用额外的资源(比如图片、CSS、JavaScript),这些资源文件都会通过网络加载(或通过缓存)。当主线程在解析数据并构建 DOM 树时,会逐一找出这些文件并加载。为了加速页面的显示, 预加载扫描(preload scanner)会同时在后台运行。如果页面上有 <img>
或是 <link>
之类的需要加载的资源,当解析器生成相应的标签时,预加载器就会通知网络进程去加载资源。
2.样式计算(Recalculate Style) |
仅仅有了 DOM 树结构,浏览器并不能确定页面的呈现,因为我们还可以通过 CSS 为元素设置更丰富样式。主线程会解析 CSS 并通过 CSS 选择器来确定出每个 DOM 节点的样式信息。
-
把CSS文本转换为浏览器可理解的样式结构 CSS->styleSheets。
可以在控制台键入document.styleSheets
查看样式表 -
将样式表中的属性值标准化
并非所有的数据类型都能轻易的被渲染引擎理解,所以需要将这些不容易被理解的值标准化。
-
遵循继承和层叠规则计算出DOM树中每个节点的具体样式
遵循这两个规则下,这个阶段最终输出的内容是每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内。
加载CSS需注意 |
在构建 DOM Tree 的过程中,如果遇到 link
标记,浏览器就会立即发送请求获取样式文件。当然我们也可以直接使用内联样式或嵌入样式,来减少请求;但是会失去模块化和可维护性,并且像缓存和其他一些优化措施也无效了,利大于弊,性价比实在太低了;除非是为了极致优化首页加载等操作,否则不推荐这样做。
CSS阻塞页面渲染 |
CSS 的加载和解析并不会阻塞 DOM Tree 的构建,因为 DOM Tree 和 styleSheets 是两个相互独立的结构。但是这个过程会阻塞页面渲染,也就是说在没有处理完 CSS 之前,文档是不会在页面上显示出来的,这个策略的好处在于页面不会重复渲染;如果 DOM Tree 构建完毕直接渲染,这时显示的是一个原始的样式,等待 styleSheets 构建完毕,再重新渲染又会突然变成另外一个模样,除了开销变大之外,用户体验也是相当差劲的。另外 link
标记会阻塞 JavaScript 运行,在这种情况下,DOM Tree 是不会继续构建的,因为 JavaScript 也会阻塞 DOM Tree 的构建,这就会造成很长时间的白屏。
3.布局 |
有了DOM树和DOM树中元素的样式,但还不足以显示页面,因为不知道DOM的几何位置信息。计算DOM树中可见元素的几何位置就成为布局。
Chrome 在布局阶段需要完成两个任务:创建布局树和布局计算。
创建布局树 |
DOM树中有许多是不可见的元素,比如head标签,还有使用了dispaly:none属性的元素。那么在显示之前,我们还要额外地构建一棵只包含可见元素布局树(可见DOM元素+DOM元素样式)。
布局计算 |
有了一棵完整的布局树,接下来就是计算树节点的坐标位置。过程有点复杂,有兴趣自行百度。简而言之,就是计算布局树中各个可见元素在页面的坐标位置。
注:对谷歌浏览器而言,渲染树也是16年之前的东西了,现在的代码完全重构了,可以把LayoutTree看成是渲染树,不过和之前的渲染树还是有一些差别的。
4.分层 |
有了布局树,知道了各个元素的位置信息,依然还不能绘制页面。原因在于:页面并非单纯的平铺就可以了,实际上,页面还有许多复杂的3D变换、页面滚动、z-index排序等。为此,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。
❓怎么看图层?
Chrome 的开发者工具,选择Layers标签
可见,浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。
图层与布局树有什么关系呢? |
并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
什么情况下渲染引擎才会为特定的节点创建新的渲染图层呢?
拥有层叠上下文属性的元素
页面是个二维平面,但是层叠上下文能够让 HTML 元素具有三维概念
从图中可以看出,明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文层叠上下文属性。目前,说的都只是渲染层层面的,用到的硬件加速是在哪里呢?那是对复合图层而言的,不在此展开,自行了解 浏览器渲染图层VS复合图层(硬件加速)
5.图层绘制 |
要知道最终的成像是在显示器上的,而对于计算机而言,它是不懂图层树这样的结构的。在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,也就是生成绘制指令。
那么,你要知道绘制是什么。比如:画一个正方形吧。也就说你有一个明确的目标(绘制正方形),然后你要知道长宽还有起始坐标,边线的颜色粗细等等。先绘制什么,后绘制什么,这都是可以拆分为很多小的绘制指令。然而每个图层的元素可不止一个两个,每个元素就是一个绘制块,一个绘制块就由好多条绘制指令组成。多个绘制快就组成一个绘制列表,也就是进入一个绘制队列,有先后顺序。
在图层绘制阶段,输出的内容就是这些待绘制列表 ,绘制列表就是用来记录绘制顺序和绘制指令的列表。
打开Layers面板,双击左侧document节点,下面就会展开一个Profiler面板,里面有清晰的绘制流程,懂canvas的同学应该对这些指令会有点眼熟。
6.分块(tile) |
上一步只是得到了绘制列表,而实际上绘制操作是由渲染引擎中的合成线程(Compositor thread)来完成的。
如上图,主线程会把绘制列表提交到合成线程,那具体会做些什么?
在此之前,我们有必要了解一下什么是视口,视口就是屏幕上页面的可见区域。以CSDN首页为例:我们能看到的只有红色框部分,其余部分需要滚动下去看
有的图层可以很大,通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。
基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小有限制,比如长/宽必须是2的幂次方,最大不能超过2048或者4096等。
7.栅格化(raster) |
合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,将几何信息转换为屏幕上的像素的过程。 图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,(没有启用硬件加速的浏览器)所有的图块栅格化都是在线程池内执行的。通常,栅格化过程都会使用 GPU 来加速生成,GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU(对GPU而言并非图块而称之为纹理了),那么最终生成的位图被保存在 GPU 内存中(跨进程操作)。
合成线程可以给不同的光栅线程赋予不同的优先级(prioritize),进而使那些在视口中的或者视口附近的页面可以先被光栅化。为了响应用户对页面的放大和缩小操作,页面的图层(layer)会为不同的清晰度配备不同的图块。
拓展:栅格化如果采用了GPU加速,是否可认为整个浏览器页面都是采用硬件加速绘制的
这只能说明在渲染流水线的某小阶段采用了GPU硬件加速,并非说浏览器开启了硬件加速那么浏览器页面就是交给GPU处理的。实际上还是需要经由渲染引擎一步步处理,到了光栅化这一步才交给GPU处理。而我们说的硬件加速,是在知道DOM树和样式之后便独立出一个复合图层,直接跳过了布局分层绘制阶段,只会触发绘制后的渲染流水线更新,一般作用于CSS 动画中。有兴趣的可以了解下:浏览器渲染图层VS复合图层(硬件加速)
8.合成和显示 |
当图层上面的图块都被栅格化后,合成线程会收集图块上面叫做绘画四边形(draw quads)的信息来构建一个合成帧(compositor frame)。
绘画四边形(DrawQuad ):包含图块在内存的位置以及图层合成后图块在页面的位置之类的信息。
合成帧:代表页面一个帧的内容的绘制四边形集合。
上面的步骤完成之后,合成线程就会通过IPC向浏览器进程提交一个渲染帧。
浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,合成帧都会被发送给GPU从而展示在屏幕上。如果合成线程收到页面滚动的事件,合成线程会构建另外一个合成帧发送给GPU来更新页面。(对于浏览器而言,最后一步是交给浏览器进程处理的)
经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。
电脑显示屏是怎么显示出图像的?
|
当然你写确切看到浏览器各个过程是怎么执行的,打开谷歌开发者工具的Performance面板,你会得到你想要的答案的:
延伸 三个和渲染流水线相关的概念:“重排”“重绘”和“合成”与优化
===================================================================================
参考文档:
inside-browser-part3
inside-browser-part4
HTML、CSS和JavaScript,是如何变成页面的
【精简版】浏览器渲染机制(完整流程概述)
更多推荐
页面的渲染流程(Chrome)
发布评论