singsong: 文本是本身看了一些不错资料整理出来的,对该知识点感兴趣的同窗能够查看参考文章小节。css
主要为 CSS 优化工做打一下基础。要编写高性能的网站和应用,除了确保编写的代码尽量高效地运行外,还须要确保页面性能,刷新频率尽可能到达 60FPS。这就须要了解浏览器是如何进行渲染的。而浏览器渲染与 CSS 密切相关,所以只有了解其中工做原理才能让 CSS 更好地工做。html5
另外,接下来会出一篇优化实战文章,会涉及 JavaScript 和 CSS 一些优化。其中关于 JavaScript 的优化以前已进行过介绍:常见的 JavaScript 内存泄露。本文是对 CSS 优化进行一个补充。git
本文主要介绍浏览器的渲染,即渲染引擎(Rendering engine)负责的工做: 将请求的 HTML 和 CSS 内容解析并渲染在屏幕上。github
DOM:Document Object Model,文档对象模型。它能够以一种独立于平台和语言的方式,访问和修改一个文档的内容和结构。它定义了一组与平台,语言无关的接口,该接口容许编程语言动态地访问和修改结构化文档。基于 DOM 表示的文档被描述成一个树形结构,使用 DOM 的接口能够对 DOM 树进行操做。web
这里以一个实例展开讲解,以下 HTML 结构包含一些文本和一张图片:chrome
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
复制代码
浏览器是如何处理这个 HTML 页面:编程
<html>
、<body>
,以及其余尖括号内的字符串。每一个 token 都具备必定特殊含义和规则。整个过程的最终输出是 HTML 页面的 DOM tree,后续浏览器对页面进一步的全部处理都会用到它。浏览器每次处理 HTML 标签时,都会完成以上全部步骤:将字节转换成字符,肯定 token,将 token 转换成节点,而后构建 DOM tree。canvas
CSSOM:CSS Object Model,CSS 对象模型。CSSOM 定义了 JavaScript 访问样式的能力和方式。它是在 DOM 中的一些接口中,加入获取和操做 CSS 属性或接口的 JavaScript 接口,于是 JavaScript 能够动态操做 CSS 样式。DOM 提供了接口让 JavaScript 修改 HTML 文档,CSSOM 提供了接口让 JavaScript 得到和修改 CSS 代码设置的样式信息。后端
在浏览器构建 DOM 遇到 link 标签时,该标签引用一个外部 CSS 样式表:style.css。因为预见到须要利用该资源来渲染页面,它会当即发出对该资源的请求,并返回如下内容:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
复制代码
与处理 HTML 时相似,须要将收到的 CSS 规则转换成某种浏览器可以理解和处理的内部表示。所以会重复 HTML 过程,不过是对 CSS 而不是 HTML:
将 CSS 字节转换成字符,接着转换成 token 和节点,最后将它们连接到 CSSOM tree 结构中:
CSSOM tree 能够用于肯定节点对象的计算样式。如 span 标记包含了color:red
样式和继承于 body 标记的font-size:16px
样式;
在 DOM tree 中,存在不可见与可见节点之分。顾名思义,不可见节点是不须要绘制最终页面中的节点,如meta
、head
、script
等,以及经过 CSS 样式display:none
隐藏的节点。相反可见节点是用户可见的,如body
、div
、span
、canvas
、img
等。对于这些可见节点,浏览器须要将它们的内容绘制到最终的页面中,因此浏览器会为它们创建对应的 RenderObject 对象。一个 RenderObject 对象保存了为绘制 DOM 节点的各类信息。这些 RenderObject 对象与 DOM 对象相似,也构成一棵树,称为RenderObject tree。RenderObject tree 是基于 DOM tree 创建起来的一棵新树,是为了布局计算和渲染等机制而构建的一种新的内部表示。RenderObject tree 节点与 DOM tree 节点不是一一对应关系。由于建立一个 RenderObject 对象须要知足以下规则:
DOM tree 的 document 节点
DOM tree 中的可见节点,如html
、body
、div
等。而浏览器不会为不可见节点建立 RenderObject 节点。
某些状况下浏览器须要建立匿名的 RenderObject 节点,该节点不对应 DOM 树中的任何节点。
RenderObject 对象构成了 RenderObject tree,每一个 RenderObject 对象保存了为绘制 DOM 节点的计算样式。RenderObject tree 也能够理解成由 CSSOM tree 和 DOM tree 合并成:
当浏览器建立 RenderObject 对象以后,每一个对象并不知道本身在设备视口内的位置、大小等信息。浏览器根据盒模型(Box-model)来计算它们的位置、大小等信息的过程称为布局计算(重排)。布局计算是一个递归的过程,这是由于一个节点的大小一般须要先计算它的孩子节点的位置、大小等信息。为了计算节点在页面中的确切大小和位置,浏览器会从 RenderObject tree 的根节点开始进行遍历。
实例:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critial Path: Hello world!</title>
</head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!</div>
</div>
</body>
</html>
复制代码
页面包含了两个嵌套 div:父 div 将其的显示尺寸设置为 viewport 宽度的 50%,子 div 将其宽度设置为其父项的 50%,即 viewport 宽度的 25%。
浏览器渲染引擎并非直接使用 RenderObject tree 进行绘制,为了方便处理 Positioning(定位),Clipping(裁剪),Overflow-scroll(页內滚动),CSS Transform/Opacity/Animation/Filter,Mask or Reflection,Z-indexing(Z 排序)等,浏览器须要会为一些特定的 RenderObject 生成对应的 RenderLayer,并生成一棵对应的 RenderLayer tree。而这些特定的 RenderObject 跟对应的 RenderLayer 就是直属的关系,若是它们的子节点若是没有对应的 RenderLayer,就从属于父节点的 RenderLayer。最终,每个 RenderObject 都会直接或者间接地从属于一个 RenderLayer。所以 RenderObject 节点与 RenderLayer 节点不是一一对应关系,而是一对多的关系。那须要知足什么条件,渲染引擎才为 RenderObject 创建对应的 RenderLayer:
<canvas>
element that has a 3D (WebGL) context or an accelerated 2D context<video>
element翻译:
每一个 RenderLayer 对象能够想象成图像中一个图层,各个图层叠加构成了一个图像。浏览器会遍历 RenderLayer tree,再遍历从属这个 RenderLayer 的 RenderObject,RenderObject 对象存储有绘制信息,并进行绘制。RenderLayer 和 RenderObject 共同决定了最终呈现的网页内容,RenderLayer tree 决定了网页的绘制的层次顺序,而从属于 RenderLayer 的 RenderObject 决定了该 RenderLayer 的内容。
在完成构建 RenderLayer tree 以后,浏览器会使用图形库将其构建的渲染模型绘制出来,该过程分为两个阶段:
渲染引擎的渲染,目前有三种网页的渲染方式:
- 硬件加速合成(Accelerated Compositing):使用 GPU 来完成合成工做。
- 合成化渲染:使用合成(compositing)技术的渲染称。
第二种和第三种渲染方式,都是使用了合成化渲染技术,合成工做也都是由 GPU 来作。对于常见的 2D 绘图操做,使用 GPU 来绘图不必定比使用 CPU 绘图在性能上有优点,例如绘制文字、点、线等。缘由是 CPU 的使用缓冲机制有效减小了重复绘制的开销并且不须要考虑与 GPU 并行。另外,GPU 的内存资源相对 CPU 的内存资源来讲比较紧张,并且网页的分层使得 GPU 的内存使用相对比较多。鉴于此,就目前的状况来看,三者都存在是有其合理性的,下面分析一下它们的特色:
浏览器还可使用多线程的渲染架构,将网页内容绘制到后端存储的操做放到另一个独立的线程(绘制线程),而原来线程转为合成线程,绘制线程跟合成线程之间可使用同步,部分同步,彻底异步等做业模式,让浏览器能够在性能与效果之间根据须要进行选择。
对于软件渲染而言,到 RenderLayer tree 就结束了,后面不会创建其它额外的树来对应于 RenderLayer tree。可是,对于硬件渲染来讲,在 RenderLayer tree 以后,浏览器渲染引擎为硬件渲染提供了更多的内部结构来支持这个机制。
在硬件加速渲染的合成化渲染和软件绘图的合成化渲染架构下,一个 RenderLayer 对象若是须要后端存储,它会建立一个 RenderLayerBacking 对象,该对象负责 Renderlayer 对象所须要的各类存储。理想状况下,每一个 RenderLayer 均可以建立本身的后端存储,事实上不是全部 RenderLayer 都有本身的 RenderLayerBacking 对象。若是一个 RenderLayer 对象被像样的建立后端存储,那么将该 RenderLayer 称为合成层(Compositing Layer)。
哪些 RenderLayer 对象能够是合成层?若是一个 RenderLayer 对象具备如下的特征之一,那么它就是合成层:
翻译:
<video>
元素<canvas>
元素opacity
或transform
动画每一个合成层都有一个 RenderLayerBacking,RenderLayerBacking 负责管理 RenderLayer 所须要的全部后端存储,由于后端存储可能须要多个存储空间。在浏览器(WebKit)中,存储空间使用类 GraphicsLayer 来表示。浏览器会为这些 RenderLayer 建立对应的 GraphicsLayer,不一样的浏览器须要提供本身的 GrphicsLayer 实现用于管理存储空间的分配,释放,更新等等。拥有 GrphicsLayer 的 RenderLayer 会被绘制到本身的后端存储,而没有 GrphicsLayer 的 RenderLayer 它们会向上追溯有 GrphicsLayer 的父/祖先 RenderLayer,直到 Root RenderLayer 为止,而后绘制在有 GrphicsLayer 的父/祖先 RenderLayer 的存储空间,而 Root RenderLayer 老是会建立一个 GrphicsLayer 并拥有本身独立的存储空间。在将每一个合成图层包含的 RenderLayer 内容绘制在合成层的后端存储中,这里绘制能够是软件绘制或硬件绘制。接着由合成器(Compositor)将多个合成层合成起来,造成最终用户可见的网页,实际上就是一张图片。
GraphicsLayer 又构成了一棵与 RenderLayer 并行的树,而 RenderLayer 与 GraphicsLayer 的关系有些相似于 RenderObject 与 RenderLayer 之间的关系。以下是 DOM tree、RenderObject tree、RenderLayer tree、GraphicsLayer tree关系图:
这样能够合并一些 RenderLayer 层,从而减小内存的消耗。其次,合并以后,减小了合并带来的重绘性能和处理上的困难。在硬件加速渲染的合成化渲染和软件绘图的合成化渲染架构下,RenderLayer 的内容变化,只须要更新所属的 GraphicsLayer 的缓存便可,而缓存的更新,也只须要绘制直接或者间接属于这个 GraphicsLayer 的 RenderLayer,而不是全部的 RenderLayer。特别是一些特定的 CSS 样式属性的变化,实际上并不引发内容的变化,只须要改变一些 GraphicsLayer 的混合参数,而后从新混合便可,而混合相对绘制而言是很快的,这些特定的 CSS 样式属性咱们通常称之为是被加速的,不一样的浏览器支持的情况不太同样,但基本上 CSS Transform & Opacity 在全部支持混合加速的浏览器上都是被加速的。被加速的 CSS 样式属性的动画,就比较容易达到 60 帧/每秒的流畅效果了。
不过并非拥有独立缓存的 RenderLayer 越多越好,太多拥有独立缓存的 RenderLayer 会带来一些严重的反作用:
一般一个合成层的后端存储被分割成多个大小相同的瓦片状的小存储空间,每一个瓦片能够理解为 OpenGL 中的一个纹理,合成层的结果被分开存储在这些瓦片中。为何使用瓦片化的后端存储?
网页加载后,绘制新的每一帧,通常都须要通过计算布局(layout)、绘图(paint)、合成(composite)三阶段。所以要想提升页面性能(或 FPS),须要减小每一帧的时间。而在这三个阶段中,layout 和 paint 比较耗时间,而合成须要的时间相对较少一些。
若是修改 DOM 元素的 layout 样式(如 width, heihgt 等),浏览器会计算页面须要 relayout 的元素,而后触发一个 relayout。被 relayout 的元素,接着会执行绘制,最后进行渲染合并生成页面。
若是修改 DOM 元素的 paint 样式(如 color, background 等),浏览器会跳过布局,直接执行绘制,再进行合成。
若是修改 DOM 元素的 composite 样式(如 transform, opacity 等)。浏览器会跳过布局和绘制,直接执行合成。该过程是开销最小的,也是优化着手点。
若是想知道修改任何指定 CSS 样式会触发 layout、paint、composite 中的哪个,请查看CSS 触发器。
能够经过什么途径进行优化,减小每一帧的时间(避免过多 layout 或 paint):
即便用 GPU 硬件加速,为某些 RenderLayer 建立对应 GraphicsLayer。经过为每个合成层设置transform
属性来完成 transition 或 animation,有效地避免 relayout 和 repaint 的开销。
本文重点介绍了浏览器渲染引擎的渲染过程,涉及了 DOM tree、CSSOM tree、RenderObject tree、RenderLayer tree、GraphicsLayer tree。并对各类渲染模式进行了简单介绍,其中引入了硬件加速机制,还给出一些优化建议。了解这些知识点对咱们开发高性能的 web 应用会有很大的帮助。