问题1:transform动画为什么没有经过大量的重绘?
解答:为什么 transform
没有触发
repaint 呢?(1)简而言之,transform
动画由GPU控制,支持硬件加速,并不需要软件方面的渲染。(2)浏览器接收到页面文档后,会将文档中的标记语言解析为DOM树。DOM树和CSS结合后形成浏览器构建页面的渲染树。渲染树中包含了大量的渲染元素,每一个渲染元素会被分到一个图层中,每个图层又会被加载到GPU形成渲染纹理,而图层在GPU中transform
是不会触发
repaint 的,这一点非常类似3D绘图功能,最终这些使用 transform
的图层都会由独立的合成器进程进行处理。(3)3D
和 2D transform 的区别就在于,浏览器在页面渲染前为3D动画创建独立的复合图层,而在运行期间为2D动画创建。动画开始时,生成新的复合图层并加载为GPU的纹理用于初始化 repaint。然后由GPU的复合器操纵整个动画的执行。最后当动画结束时,再次执行 repaint 操作删除复合图层。(4)如果某一个元素的背后是一个复杂元素,那么该元素的
repaint 操作就会耗费大量的资源,此时也可以使用上面的技巧来减少性能开销(transform)。内容摘自CSS动画之硬件加速 CSS3
Filter的十种特效
问题2:浏览器可能牵涉的渲染过程?
recaculate style:重新计算显示样式
layout:计算布局
update layer tree:更新图层树
paint:重绘
composite layers:组合层
update layer tree:更新图层树
3.网页的样式计算和布局计算?
renderobject:对于所有的可视节点(script,meta,head等除外)webkit都会建立renderobject对象,该对象保存了为绘制dom节点所必需的各种信息,例如样式布局信息,经过webkit处理后renderobject对象知道如何绘制自己。下面情况都会为dom节点建立renderobject对象:
dom树的document节点;dom树中的可视节点,如html,div等,webkit不会为非可视节点创建renderobject对象;某些情况下需要创建匿名的renderobject对象,其不对应dom树中任何节点,只是webkit处理上的需要,典型的就是匿名的renderblock节点
renderobject树:这些renderobject对象同dom节点类似,也构成一棵树,称为renderobject树。
注意:renderobject树时基于dom树建立的一颗新树,是为了布局计算和渲染等机制建立的一种新的内部表示.如果dom树中被动态添加了新的节点,webkit也需要创建相应的renderobject对象
注意:从上图可以看出htmldocument节点对应于renderview节点,renderview节点时renderobject树的根节点。同时head元素也没有创建renderobject对象
dom树建立之后->css解析器和规则匹配->renderobject树建立。css解析器和规则匹配处于dom树建立之后,renderobject树建立之前,css解释后的结果会保存起来,然后renderobject树基于该结果进行规范匹配和布局计算。当网页有用户交互和动画等动作的时候通过cssom等技术,js代码同样可以方便的修改css代码,webkit此时需要重新计算样式并重复以上过程。
当webkit创建了renderoject对象后每个对象都是不知道自己的位置,大小等信息的(实际的布局计算在renderobject类中),webkit根据框模型计算他们的位置大小等信息的过程称为布局计算或者排版。布局计算分为两类:第一类是对整个renderobject树进行计算。第二类是对renderobject中某个子树的计算,常见于文本元素活着overflow:auto块的计算,这种情况一般是子树布局的改变不会影响其周围元素的布局,因此不需要计算更大范围内的布局。
布局计算是一个递归的过程,这是因为一个节点的大小通常需要计算他的子女节点的位置大小等信息。步骤如下:
首先,函数(renderobject的layout函数)判断renderobject节点是否需要重新计算。通常需要检查位数组中的相应标记位,子女是否要重新计算等
其次,函数确定网页的宽度和垂直方向上的外边距,这是因为网页通常是在垂直方向上滚动而垂直方向上尽量不需要滚动。
再次,函数会遍历每一个子女节点,依次计算他们的布局。每一个元素会实现自己的layout函数,根据特定的算法来计算该类型元素的布局,如果页面元素定义了自身的宽高。那么webkit按照定义的宽高来确定元素的大小,而对于文字节点这样的内联元素需要结合字号大小和文字的多少来确定对应的宽高。如果页面元素所确定的宽高超出了布局容器包含快所提供的宽高,同时overflow为visible或者auto,webkit会提供滚动条显示所有内容。除非网页定义了页面元素的宽高,一般来说页面元素的宽高实在布局的时候通过计算得到的。如果元素有子女元素那么需要递归这个过程。
最后,节点依据子女们的大小计算的高度得到自己的高度,整个过程结束。那么哪些情况下需要重新计算:
首先,网页首次打开的时候,浏览器设置网页的可是区域,并调用计算布局的方法。这也是一个可见的场景,就是当可视区域发生变化的时候,webkit都需要重新计算布局,这是因为网页块大小发生了变化(rem时候很显然)
其次,网页的动画会触发布局计算,当网页显示结束后动画可能改变样式属性,那么webkit需要重新计算
然后,js代码通过cssom等直接修改样式信息,也会触发webkit重新计算布局
最后,用户的交互也会触发布局计算,如翻滚网页,这会触发新区域布局的计算
注意:布局计算相对比较耗时,一旦布局发生变化,webkit就需要后面的重绘制操作。另一方面,减少样式的变动而依赖现在html5新功能可能有效的提高网页的渲染效率。
4.网页层次和renderlayer树?
网页是可以分层的,原因之一是方便网页开发者开发网页并设置网页的层次,二是为了webkit处理上的便利,也就是说为了简化渲染的逻辑。webkit会为网页的层次创建相应的renderlayer对象。当某些类型的renderobject的节点或者具有某些css样式的renderobject节点出现的时候,webkit就会为这些节点创建renderlayer对象。一般来说,某个renderobject节点的后代的都属于该节点,除非webkit根据规则为某个后代的renderobject节点创建了一个新的renderlayer对象。
注意:renderlayer树时基于renderobject树建立起来的一颗新树,而且renderlayer节点和renderobject节点不是一一对应关系,而是一对多的关系。下面的情况renderobject对象需要建立新的renderlayer节点:
(1)dom树的document节点对应的renderview节点
(2)dom树中的document的子女节点,也就是html节点对应的renderblock节点
(3)显示的指定css位置的renderobject对象
(4)有透明效果的renderobject对象
(5)有节点溢出(overflow),alpha或者反射等效果的renderobject对象
(6)使用canvas2d和3d(webgl)技术的renderobject对象
(7)video节点对应的renderobject对象
下面是renderobject树和renderlayer树对应关系:
注意:上图renderlayer树应该包含三个renderlayer节点-根节点,子女节点以及叶子节点
除了跟节点,也就是renderlayer节点,一个renderlayer节点的父亲就是该renderlayer节点对应的renderobject节点的祖先链中最近的祖先,并且祖先所在的renderlayer节点同该节点的renderlayer节点不同。基于这个原理,这些renderlayer节点也就构成了一个renderlayer树。每个renderlayer节点包含的renderobject节点都是一个renderobject子树,理想情况下,每个renderlayer对象都有一个后端类,这个后端类涌来存储改renderlayer对象的绘制结果。renderlayer节点可以有效的减少网页的复杂程度,并且在很多情况下能够减少页面重新渲染的开销。注意:renderlayer没有子类,但是renderobject有子类的概念,renderlayer只是表示网页的一个层次,没有子层次的概念。
<!doctype html> <html> <head> <title></title> <style type="text/css"> video,div,canvas{ -webkit-transform:rotateY(30deg) rotateX(-45deg); } </style> </head> <body> <video src='vidwo.mp4'></video> <div> <canvas id='a2d'></canvas> <canvas id='a3d'></canvas> </div> <script type="text/javascript"> var size=300; var a2dCtx=document.getElementById('a2d').getContext('2d'); a2dCtx.canvas.width=size; a2dCtx.canvas.height=size; a2dCtx.fillStyle='rgba(0,192,192,80)'; a2dCtx.fillRect(0,0,200,200); var a3dCtx=document.getElementById('a3d').getContext('experimental-wbgl'); a3dCtx.canvas.width=size; a3dCtx.canvas.height=size; a3dCtx.clearColor(0.0,192.0/255.0,80.0/255.0); a3dCtx.clear(a3dCtx.COLOR_BUFFER_BIT); </script> </body> </html>
这个例子就会创建四个层,如下图:
所谓的根层就是跟节点创建的层,它对应着整个网页的文档对象。video对象创建一个层可以有效的处理视频解码器和浏览器之间的交互和渲染。同时需要3d转换的元素也会创建相应的层。位置上根层在最后,层3和层4在最前面。webkit创建新层实际上是为了渲染引擎处理上的方便和高效。
5.webkit的渲染过程?
第一阶段:从网页的url到构建完dom树
具体示意图如下:
注意:网页在加载和渲染过程中会发出domcontent事件和dom的onload事件,分别在dom树构建完成以及dom树构建完并且网页所依赖的资源都加载完成之后。
具体过程如下:
1.网页输入URL时候,webkit调用其资源加载器(总共有三类:特定资源加载器如imageloader,资源缓存机制的资源加载器如cachedresourceloader,通用资源加载器resourceloader)加载该URL对应的网页
2.加载器依赖网页模块建立连接,发起请求并接受回复
3.webkit接受到各种网页或者资源的数据,其中某些资源可能是异步的或者同步的
4.网页被加载给html解释器变成一系列的词语(token)
5.解析器根据词语构建节点node,形成dom树
6.如果节点是js代码的话,调用js引擎解释并执行
7.js代码可能会修改dom树的结构
8.如果节点需要依赖其他资源,例如图片,css,视频等,调用资源加载器加载他们,但是他们是异步的,不会阻碍当前dom树的构建。如果是js资源,那么需要停止当前dom树的构建,直到js资源加载并将被js引擎执行后才继续dom树的构建。我们看看html解析器的解析过程:
第二阶段:从dom树到构建完webkit绘图上下文(webkit利用css和dom树构建renderobject树直到绘图上下文)。具体过程如下:
注意:renderobject树的建立并不表示dom树被销毁,事实上上面四个内部表示结构一直存在,直到网页被销毁,因为他们对于网页的渲染起了很大的作用
1.css文件被css解析器解释成为内部表示结构
2.css解析器工作完成之后,在dom树上附加解释后的样式信息,这就是renderobject树
3.renderobject节点在创建的同时,webkit会根据网页的层次结构创建renderlayer树,同时构建一个虚拟的绘图上下文
第三阶段:从绘图上下文到最终的图像
这一过程主要依赖于2d和3d图像库,具体过程如下:
1.绘图上下文时一个与平台无关的抽象类,它将每个绘图操作桥接到不同的具体实现类,也就是绘图具体实现类
2.绘图实现类也可能有简单的实现,也可能有复杂的实现,在chromium中,他的实现相当复杂,需要chromium合成器来完成复杂的多进程和gpu加速
3.绘图实现类将2d图形库和3d图形库绘制的结果保存下来,交给浏览器来同浏览器界面一起显示
总结:现在的网页很多是动态的网页,这意味着在渲染完成之后,由于网页的动画或者用户的交互,浏览器其实一直在不停的重复执行渲染过程
6.chrome控制台浏览器的加载过程
使用Navigation Timing API:
当load/unload动作被触发时,也可能是提示关闭当前文档时(即回车键在url地址栏中按下,页面被再次刷新,submit按钮被点击)。如果当前窗口中没有前一个文档,那么navigationStart的值就是fetchStart。
它可能是页面重定向时的开始时间(如果存在重定向的话)或者是0。
unloadEventStart:
如果被请求的文档来自于前一个同源(同源策略)的文档,那么该属性存储的是浏览器开始卸载前一个文档的时刻。否则的话(前一个文档非同源或者没有前一个文档),为0。
unloadEventEnd:
表示同源的前一个文档卸载完成的时刻。如果前一个文档不存在或者非同源,则为0。
如果存在重定向的话,redirectEnd表示最后一次重定向后服务器端response的数据被接收完毕的时间。否则的话就是0。
fetchStart是指在浏览器发起任何请求之前的时间值。在fetchStart和domainLookupStart之间,浏览器会检查当前文档的缓存。
这个属性是指当浏览器开始检查当前域名的DNS之前的那一时刻。如果因为任何原因没有去检查DNS(即浏览器使用了缓存,持久连接,或者本地资源),那么它的值等同于fetchStart。
指浏览器完成DNS检查时的时间。如果DNS没有被检查,那么它的值等同于fetchStart。
当浏览器开始于服务器连接时的时间。如果资源取自缓存(或者服务器由于其他任何原因没有建立连接,例如持久连接),那么它的值等同于domainLookupEnd。
当浏览器端完成与服务器端建立连接的时刻。如果没有建立连接它的值等同于domainLookupEnd。
可选。如果页面使用HTTPS,它的值是安全连接握手之前的时刻。如果该属性不可用,则返回undefined。如果该属性可用,但没有使用HTTPS,则返回0。
指客户端收到从服务器端(或缓存、本地资源)响应回的第一个字节的数据的时刻。
指客户端收到从服务器端(或缓存、本地资源)响应回的最后一个字节的数据的时刻。
指document对象创建完成的时刻。
指文档解析完成的时刻,包括在“传统模式”下被阻塞的通过script标签加载的内容(除了使用defer或者async属性异步加载的情况)。
当DOMContentLoaded事件触发之前,浏览器完成所有script(包括设置了defer属性但未设置async属性的script)的下载和解析之后的时刻。
当DOMContentLoaded事件完成之后的时刻。它也是javascript类库中DOMready事件触发的时刻。
如果已经没有任何延迟加载的事件(所有图片的加载)阻止load事件发生,那么该时刻将会将document.readyState属性设置为"complete",此时刻就是domComplete。
该属性返回的是load事件刚刚发生的时刻,如果load事件还没有发生,则返回0。
该属性返回load事件完成之后的时刻。如果load事件未发生,则返回0。
检测用户通过哪种方式来到此页面:
我们有几种方式来打开一个页面,例如,在地址栏输入url,刷新当前页面,通过history的前进后退。这时候 performance.navigation 就派上用场了。这个
API 有两个属性:
- redirectCount:页面请求被重定向的次数
- type:页面被载入的方式。
以下列举了 type 属性的三种取值情况:
- 0:用户通过点击链接或者在浏览器地址栏输入URL的方式进入页面。
- 1:页面重载。
- 2:通过浏览器history的前进或后退进入页面。
7.webkit渲染方式?
在构建完了dom树之后,webkit所要做的事情就是构建渲染的内部表达并使用图形库将这些模型绘制出来。网页的渲染方式有两种,第一种是软件渲染,第二种是硬件加速渲染。每一个层对应于网页中的一个或者一些可视元素,这些元素绘制内容到这个层中。如果绘图操作使用CPU来完成就叫做软件绘图,如果绘图操作使用gpu来完成,那么就叫做gpu硬件加速绘图。理想情况下每一个层都有一个绘制的存储区域,这个存储区域用于保存绘图的结果。最后需要把这些层的内容合并到同一个图像之中,叫做合成(compositing)。使用了合成技术的渲染称之为合成化渲染。
在renderobject树和renderlayer树之后,webkit的内部操作将内部模型转化为可视结果分为两个阶段:每层的内容进行绘图工作以及之后将这些绘图的结果合并为一个图像。如果对于软件渲染,那么需要使用CPU来绘制每一层的内容,但是他是没有合成阶段的,因为在软件渲染中,渲染的结果就是一个位图,绘制每一层的时候都使用这个位图,区别在于绘制的位置可能不一样,当然每一层都是按照从后到前的顺序。当然你也可以为每一层分配一个位图,但是一个位图已经可以解决所有的问题了。下面是网页的三种渲染方式:
软件渲染中网页使用的一个位图,实际上是一块CPU使用内存。第二种和第三种方式都是使用了合成化的渲染技术,也就是使用gpu硬件加速来合成这些网页,合成的技术都是使用gpu来做的,所以叫做硬件加速。但是,对于每一个层这两种方式有不同的选择。如第二种方式,某些层使用gpu而某些层使用CPU,对于CPU绘制的层,该层的结果首先当然保存在CPU内存中,之后被传输到gpu的内存中,这主要是为了后面的合成工作。第三种方式使用gpu来绘制所有的合成层。第二种和第三种方式都属于硬件加速渲染方式。
那么上面三种绘图的区别:
首先,对于常见的2d绘图操作,使用gpu来绘图不一定比CPU绘图在性能上有优势,因为CPU的使用缓存机制有效减少了重复绘制的开销而且不需要gpu并行性。其次,gpu的内存资源相对于CPU的内存资源来说比较紧张,而且网页的分层似的gpu的内存使用相对比较多。
软件渲染:浏览器最早的渲染机制,比较节省内存特别是宝贵的gpu内存,但是软件渲染只能处理2d方面的操作。简单的网页没有复杂绘图或者多媒体方面的需求,软件渲染就适合处理这种类型的网页。但是如果遇到html5新技术那么软件渲染就无能为力,一是因为能力不足,如css3d,webGL;二是因为性能不好,如canvas2d和视频。因此软件渲染被用的越来越少,特别是移动领域。软件渲染和硬件加速渲染另外一个很不同的地方在于对更新区域的处理,当网页有一个更小型区域的请求如动画时,软件渲染只要计算极小的区域,而硬件渲染可能需要重绘其中的一层或者多层,然后再合成这些层,硬件渲染的代价可能大得多。
硬件加速的合成:每一个层的绘制和所有层的合成均使用gpu硬件来完成,这对需要使用3d绘图的操作来说特别合适。在这种方式下,在renderlayer树之后,webkit和chromium还需要建立更多的内部表示,例如graphiclayer(renderlayer中前景和背景层需要的一个后端存储)树,合成器中的层(如chromium的cclayer等),目的是支持硬件加速,这显然会消耗更多的内存资源。但是,一方面,硬件加速能够支持现在所有的回html5定义的2d或者3d绘图标准;另外一方面,关于更新区域的讨论,如果需要更新某个层的一个区域,因为软件渲染没有为每一层提供后端存储,因而它需要将和这个区域有重叠部分的所有的层次相关区域依次向后向前重新绘制一遍,而硬件加速渲染只是需要重新绘制更新发生的层次,因而在某些情况下,软件渲染的代价更大,当然,这取决于网页的结构和渲染策略。
软件绘图的合成化渲染方式结合了前面两中方式的优点,这是因为很多网页可能既包含了基本的html5元素也包含html5新功能,使用CPU绘图方式来绘制某些层,使用gpu绘图方式来绘制其他一些层。原因是前面所说的性能和内存综合考虑。
8.webkit软件渲染技术
很多情况下,也就是没有哪些硬件加速内容的时候(css3变形,变换,webgl,视频),webkit可以使用软件渲染来完成页面的绘制工作。软件渲染需要关注两个方面,分别是renderlayer树和renderlayer树包含的renderobject树:
webkit如何遍历renderlayer树来绘制每一个层?
对于每一个renderobject对象,需要三个阶段绘制自己。第一阶段:绘制该层中的所有块的背景和边框 第二阶段:绘制浮动内容 第三阶段:前景也就是内容部分,轮廓等部分。注意:内联元素的背景,边框,前景都是在第三阶段被绘制的,这是不同之处。注意:在最开始的时候,也就是webkit第一次绘制网页的时候,webkit绘制的区域等同于可视区域的大小,而在这之后,webkit只是首先计算需要更新的区域,然后绘制同这些区域有交集的renderobject节点。这也就是说,如果更新区域跟某个renderlayer节点有交集,webkit会继续查找renderlayer树中包含derenderobject子树中的特定的一个或者一些节点而不是绘制整个renderlayer对应的rendeobject子树。
webkit软件渲染结果的存储方式,在不同的平台上可能不一样,但是基本上都是CPU内存的一块区域,多数情况下是一个位图。至于这个位图如何处理,如何和之前绘制的结果进行合并,如何显示出来,都和webkit的不同移植有关。
9.webkit硬件加速
对于gpu绘图而言,通常不像软件渲染那样知识计算其中更新的区域,一旦有更新请求,如果没有分层,引擎可能需要重绘所有的区域。因为计算更新部分对gpu来说可能耗费更多的时间,当网页分层之后部分区域的更新可能只在网页的一层或者几层,而不需要把整个网页进行重绘。通过重新绘制网页的一层或者几层,将他们和其他之前绘制完的层合并起来,既能使用gpu的能力,又能够减少重绘的开销。理想情况下,每一个renderlayer都有一个后端存储(renderlayerbacking对象),但是实际上都是不一样的,主要原因在于实际中的硬件能力和资源有限,为了节省gpu内存资源,硬件加速机制在renderlayer树建立之后需要做三件事情来完成网页的渲染:
第一:webkit决定将哪些renderlayer对象组合在一起,形成一个有后端存储的新层,整个新层不久后用于之后的合成,这里称为合成层(compositing layer)。每一个新层都有一个或者多个后端存储,这里的后端存储可能是gpu内存。对于一个renderlayer对象,如果它没有后端存储的新层,那么就使用其父亲所使用的合成层。
第二:将每个合成层包含的这些renderlayer内容绘制在合成层的后端存储中,这里的绘制可能是软件绘制也可能是硬件绘制
第三:由合成器将多个合成层合成起来,形成网页最终可视化结果,实际上就是一张图片。合成器是一个能够将多个合成层按照这些层的先后顺序,合成层的3d变形等设置二合成一个图像结果的设施。如果一个renderlayer对象具有以下特征之一那么就是合成层:
(1)renderlayer具有css3d属性活着css透视效果
(2)renderlayer包含的renderobject节点表示的是使用硬件加速的视频解码技术的html5的video元素
(3)renderlayer包含的renderobject节点表示的是使用硬件加速的canvas2d元素或者webgl技术
(4)renderlayer使用了css透明效果的动画或者css变换的动画
(5)renderlayer使用了硬件加速的css
filters技术
(6)renderlayer使用了剪裁(clip)或者反射(reflection)属性,并且他的后代中包含了一个合成层
(7)renderlayer有一个z坐标比自己小的兄弟节点,并且该节点是一个合成层。
那么为什么要使用合成层?
首先当然是合并一些renderlayer层,可以减少内存的使用量;其二是在合并之后,尽量减少合并带来的重绘性能和处理上的困难;其三,对于那些使用单独层能够显著提升性能的renderlayer对象可以继续使用这个好处。如webgl技术的canvas元素。下面是renderlayer,renderlayerbacking,graphiclayer的对应关系:
请继续阅读续集