本文我在阅读《WEBKIT 渲染不可不知的这四棵树》这篇文章后的一个总结,对原文感兴趣的朋友可以直接移步阅读。
现代浏览器在展示一个网页的时候,主要由两大引擎进行协同工作:渲染引擎和脚本引擎(JS 引擎)。渲染引擎主要用于页面样式的呈现,脚本引擎主要负责 JavaScript 脚本的运行。本文要介绍的几个概念,是位于渲染引擎上的。
当渲染引擎将我们访问的某个网页呈现在屏幕上时,其内部做了哪些工作呢?
生成 DOM Tree
解析 HTML
首先,浏览器会根据 HTML 构建一个 DOM 树,整个解析过程如下:
1.用户回车后,浏览器获取到了当前页面的字节流(Bytes)
2.浏览器将字节流转换成字符(Character)
3.根据字符生成 Token,可以理解为确定 HTML 的层级关系
4.根据 Token 创建节点,每个节点都是一个 Node 对象,每个 Node 对象都有一个 attach
方法,用来在其上附加样式信息
5.组合 Node 节点,生成最终的 DOM 树
这里有一张图片,可以直观的表示这个过程:
生成 DOM 树.png
解析 CSS
在解析 HTML 的过程中,如果遇到 CSS,浏览器会下载这些 CSS 文件,下载完成后对 CSS 进行解析。当然,如果 CSS 是写在 HTML 页面中的,会跳过下载这一步,直接进行解析。
对 CSS 解析的结果是生成一个 CSSOM 对象,该对象中包含了各个节点的样式信息,也是一个树形结构。如果生成了 CSSOM 对象后,HTML 还没有解析完成,就会等待 HTML 解析完成,然后将 CSSOM 的信息附加到 DOM 树上,最后形成一个 Render Tree(关于 Render Tree 在后面进行说明)。
用一张图表示这个过程:
CSSOM.png
生成 RenderObjects Tree
RenderObjects Tree(Render Tree) 也是一个树形结构,其是由一个一个的 RenderObject 构成的。前面说到,每个 Node 对象上都有一个 attach
方法,用来在节点上附加样式信息。在 CSS 解析完成,生成 CSSOM 对象后,会将 CSSOM 上的样式信息附加到 DOM 树上,这时就会调用相关节点上的 attach
方法,调用 attach
方法的结果就是生成一个 RenderObject 对象(也不一定是这样,后面会进行解释)。
通过 attach
方法生成 RenderObject 对象的调用栈如下:
RenderObject
RenderObject 是一个基类,其可以派生出一系列的子类,在调用 createObject
方法生成 RenderObject 对象时,会根据 DOM 树上不同类型的 Node 节点,生成相应的子类实例。下面是一个大致的 RenderObject 类的继承关系图:
每个 RenderObject 对象上都有一个
layout
方法,用以获取对应的布局信息。当所有的 RenderObject 对象创建完毕后,就形成了一个树形结构,也就是 Render Tree。Render Tree 生成完毕后,就会调用各个 RenderObject 对象上的 layout
方法,得到相关的布局信息,浏览器会根据这些布局信息渲染页面。
RenderObject 生成规则
在生成 RenderObject 对象时,并非会对所有的节点都生成一个 RenderObject 对象,而只会针对页面上的可见节点生成 RenderObject,同时,在某些情况下还会生成匿名的 RenderObject 对象。
关于匿名 RenderObject 对象,这里引用原文的一段描述:
根据 CSS 规范,inline 元素只能包含 block 元素或 inline 元素中的一种。如果包含多种,会自动创建一个匿名盒模型,这个盒模型也对应一个Anonymous RenderObject。
另外,这里的不可见元素是设置了 display:none
样式的元素,以及 HEAD
、META
、SCRIPT
等非可视化节点。对于设置 visibility:hidden
样式的元素,仍然会生成 RenderObject 对象。
生成 RenderObject 对象有以下几种情况:
- DOM 树的 Document 节点
- 页面上的可见元素
- 某些情况下会生成匿名的 RenderObject 对象
生成 RenderLayers Tree
浏览器渲染页面时是分层渲染的,有点类似于 PhotoShop 的图层,通过不同层的堆叠,最终在屏幕上形成可视化的页面。
网页上可能出现各种各样的元素,因此也会生成各种各样的 RenderObject 对象,浏览器在渲染页面时,会根据某种规则 RenderObject 进行分层,然后每个层次的 RenderObject 对象合并成一个 RenderLayer 对象,多个 RenderLayer 对象构成一个 RenderLayers Tree。
分层的规则如下:
- 根元素
- 有明确定位属性(position 不为static或者有transform属性)
- 有透明效果
- 有 overflow, alpha mask 或者 reflection 属性
- 有 CSS filter
- Canvas 2D和3D (WebGL)
- Video节点
在形成一个 RenderLayer 层后,会在 CPU 中对该层进行绘制,生成一个位图(BitMap),然后将位图作为纹理上传至 GPU,GPU 将这些纹理进行复合(Composite),并最终生成图像显示在屏幕上。
RenderLayer 更新
当页面上元素样式发生改变时,会触发 RenderLayer 的更新,具体说来可能会经过下面三个步骤:
- Reflow(重排)
- Repaint(重绘)
- Composite(复合)
当改变元素的位置信息,如宽高、margin 等,会触发 Reflow,从而使 RenderLayer 重新布局(Layout),当改变元素的颜色、透明度等信息时,会使 RenderLayer 进行重绘。经过重排或重绘之后会更新 RenderLayer 信息,然后生成位图上传至 GPU,进行 Composite。
在这三项步骤中,越靠前的操作所耗的性能越大,越靠后耗性能越小,因此在网页中减少 Reflow 和 Repaint 操作会提升页面性能。
另外,Repaint 不一定会造成 Reflow,而 Reflow 一定会造成 Repaint,同理,Composite 不一定会造成 Repaint,而 Repaint 一定会造成 Composite,以此类推。
GraphicsLayers Tree
其实,通过前面介绍的 DOM Tree 到 Render Tree 再到 RenderLayers Tree 这个步骤,已经可以完成页面的展示了,但还存在一个隐含的问题:性能问题。
前面说过,CPU 通过一些列的处理,会生成一个 RenderLayers Tree,然后将 RenderLayers Tree 中的每一层进行绘制,生成位图作为纹理上传至 GPU,然后由 GPU 进行复合呈现,这种方式也叫做软件渲染。当页面上的元素样式发生变化时,会更新 RenderLayer,对相关的元素重新再渲染一次(Reflow、Repaint、Composite),如果涉及到高耗操作如 3D、视频播放等,会严重的影响页面性能。
为此,Webkit 提供了一种机制,为一些高耗操作的 RenderLayer 提供后端存储,对于这些操作,可以跳过 Reflow 和 Repaint,直接在 GPU 中进行 Composite,这种方式也叫做硬件加速。理论上所有的 RenderLayer 都可以开启硬件加速,但太多反而会影响性能,最好只在需要特别需要的地方开启该功能(开启方式见后文)。
Webkit 通过 GraphicsLayer 类来完成后端存储功能,当有多个后端存储时,就会形成一个 GraphicsLayers Tree。
具有以下特性的 RenderLayer 会触发硬件加速:
- RenderLayer 具有 CSS 3D 属性或者 CSS 透视效果
- RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的视频解码技术的 HTML5 Video 元素
- RenderLayer 包含的 RenderObject 节点表示的是使用硬件加速的 Canvas2D 元素或者 WebGL 技术
- RenderLayer 使用了 CSS 透明效果的动画或者 CSS 变换的动画
- RenderLayer 使用了硬件加速的 CSS Filters 技术
- RenderLayer 的后代中包括了一个合成层
- 合成层上面(Z 轴上距离读者更近)的 RenderLayer 也是一个合成层
总结
本文介绍了 Webkit 渲染引擎在渲染页面时形成的几个树:
- DOM Tree
- CSSOM
- RenderObjects Tree
- RenderLayers Tree
- GraphicsLayers Tree
理解这几个概念对于我们理解浏览器的渲染方式和优化页面性能有较大的帮助。
完。
网友评论