在以前的一篇文章中:Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心!,我说过,偶然在一次对task和microtask的讨论当中,研究到了浏览器在处理完task和microtask以后执行的渲染机制,当时看到这个内容,仍是挺激动的,由于之前历来不知道我在js里更改的样式,浏览器究竟是何时、以怎样的方式渲染到界面上的,因而兴奋的写下了上述文章。javascript
最近深刻研究了这部分,发现这里有一片更广阔的新大陆,在咱们耳熟能详的重排、重绘、composite、合成层提高等概念下还有更深的的东西。这篇文章将会介绍浏览器的详细渲染过程。css
这里我在开头说的nexttick详解中说过这部分,可是只是本身看嗨了,并无做为文章重点去详细介绍,当时主要仍是说task和microtask。其实这部分是整个渲染过程的关键,涉及浏览器进行渲染的时机,因此认真说一下:html
请先点连接看html5官方规范: html5 event loop processing modelhtml5
好了,整个流程就介绍完了。前两步task和microtask相关处理再也不赘述。
重要的第三步里有3点值得关注的东西:java
页面渲染的时机介绍完了,来讲说渲染究竟是怎样一个过程。另,后文讲述的是浏览器详细过程,是实现,前文讲的是规范。css3
这张很经典的图许多人都看过,其中的概念你们应该都很熟悉,也就是这么几个步骤:
js修改dom结构或样式 -> 计算style -> layout(重排) -> paint(重绘) -> composite(合成)git
可是其中有更复杂的内容,咱们从更底层来详细说明这个过程,主要是下面这两幅图:
上图出自GPU Accelerated Compositing in Chrome
上图出自The Anatomy of a Framegithub
这部份内容基于blink、webkit内核,可是其中涉及到的重排、重绘、composite和合成层提高等环节对于各大浏览器都是一致的。web
位图
就是数据结构里常说的位图。你想在绘制出一个图片,你应该怎么作,显然首先是把这个图片表示为一种计算机能理解的数据结构:用一个二维数组,数组的每一个元素记录这个图片中的每个像素的具体颜色。因此浏览器能够用位图来记录他想在某个区域绘制的内容,绘制的过程也就是往数组中具体的下标里填写像素而已。chrome
纹理
纹理其实就是GPU中的位图,存储在GPU video RAM中。前面说的位图里的元素存什么你本身定义好就行,是用3字节存256位rgb仍是1个bit存黑白你本身定义便可,可是纹理是GPU专用的,GPU和CPU是分离的,须要有固定格式,便于兼容与处理。因此一方面纹理的格式比较固定,如R5G6B五、A4R4G4B4等像素格式, 另一方面GPU 对纹理的大小有限制,好比长/宽必须是2的幂次方,最大不能超过2048或者4096等。
Rasterize(光栅化)
在纹理里填充像素不是那么简单的本身去遍历位图里的每一个元素而后填写这个像素的颜色的。就像前面两幅图。光栅化的本质是坐标变换、几何离散化,而后再填充。
同时,光栅化从早期的 Full-screen Rasterization基本都进化到了如今的Tile-Based Rasterization, 也就是否是对整个图像作光栅化,而是把图像分块(tile,亦有翻译为瓦片、贴片、瓷片…)后,再对每一个tile单独光栅化。光栅化好了将像素填充进纹理,再将纹理上传至GPU。
缘由一方面如上文所说,纹理大小有限制,即便你整屏光栅化也是要填进小块小块的纹理中,不如事先根据纹理大小分块光栅化后再填充进纹理里。另外一方面是为了减小内存占用(整屏光栅化意味着须要准备更大的buffer空间)和下降整体延迟(分块栅格化意味着能够多线程并行处理)。
看到下图中蓝色的那些青色的矩形了吗?他们就是tiles。
能够想见浏览器的一次绘制过程就是先把想绘制的内容如文字、背景、边框等经过分块Rasterize绘制到不少纹理里,再把纹理上传到gpu的存储空间里,gpu把纹理绘制到屏幕上。
咱们先把计算样式、重排等步骤抽离,单独讲解浏览器是怎么绘制的。
先来看这幅经典的图:
图中一些名词的称呼发生了变化,详见taobaofed的文章:无线性能优化:Composite
首先咱们有DOM树,可是DOM树里面的DOM是供给JS/HTML/CSS用的,并不能直接拿过来在页面或者位图里绘制。所以浏览器内部实现了Render Object:
每一个Render Object和DOM节点一一对应。Render Object上实现了将其对应的DOM节点绘制进位图的方法,负责绘制这个DOM节点的可见内容如背景、边框、文字内容等等。同时Render Object也是存放在一个树形结构中的。
既然实现了绘制每一个DOM节点的方法,那是否是能够开辟一段位图空间,而后DFS遍历这个新的Render Object树而后执行每一个Render Object的绘制方法就能够将DOM绘制进位图了?就像“盖章”同样,把每一个Render Object的内容一个个的盖到纸上(类比于此时的位图)是否是就完成了绘制。
不,浏览器还有个层叠上下文。就是决定元素间相互覆盖关系(好比z-index)的东西。这使得文档流中位置靠前位置的元素有可能覆盖靠后的元素。上述DFS过程只能无脑让文档流靠后的元素覆盖前面元素。
所以,有了Render Layer。
固然Render Layer的出现并非简单由于层叠上下文等,好比opacity小于一、好比存在mask等等须要先绘制好内容再对绘制出来的内容作一些统一处理的css效果。
总之就是有层叠、半透明等等状况的元素(具体哪些状况请参考无线性能优化:Composite)就会从Render Object提高为Render Layer。不提高为Render Layer的Render Object从属于其父级元素中最近的那个Render Layer。固然根元素HTML本身要提高为Render Layer。
所以如今Render Object树就变成了Render Layer树,每一个Render Layer又包含了属于本身layer的Render Object。
另外:
The children of each RenderLayer are kept into two sorted lists both sorted in ascending order, the negZOrderList containing child layers with negative z-indices (and hence layers that go below the current layer) and the posZOrderList contain child layers with positive z-indices (layers that go above the current layer).
每一个Render Layer的子Render Layer都是按照升序排列存储在两个有序列表当中的:negZOrderList存储了负z-indicices的子layers,posZOrderList存储了正z-indicies的子layers。
— 出自GPU加速的compositing一文
如今浏览器渲染引擎遍历 Layer 树,访问每个 RenderLayer,而后递归遍历negZOrderList里的layer、本身的RenderObject、再递归遍历posZOrderList里的layer。就能够将一颗 Layer树绘制出来。
Layer 树决定了网页绘制的层次顺序,而从属于 RenderLayer 的 RenderObject 决定了这个 Layer 的内容,全部的 RenderLayer 和 RenderObject 一块儿就决定了网页在屏幕上最终呈现出来的内容。
层叠上下文、半透明、mask等等问题经过Render Layer解决了。那么如今:
开辟一个位图空间->不断的绘制Render Layer、覆盖掉较低的Layer->拿给GPU显示出来 是否是就彻底ok了?
不。还有GraphicsLayers和Graphics Context
上面的过程能够搞定绘制过程。可是浏览器里面常常有动画、video、canvas、3d的css等东西。这意味着页面在有这些元素时,页面显示会常常变更,也就意味着位图会常常变更。每秒60帧的动效里,每次变更都重绘整个位图是很恐怖的性能开销。
所以浏览器为了优化这一过程。引出了Graphics Layers和Graphics Context,前者就是咱们常说的合成层(Compositing Layer):
某些具备CSS3的3D transform的元素、在opacity、transform属性上具备动画的元素、硬件加速的canvas和video等等,这些元素在上一步会提高为Render Layer,而如今他们会提高为合成层Graphics Layer(你若是查看了前文我给的连接,你当时可能会疑惑为何这些状况也能提高为Render Layer,如今你应该明白了,他们是为提高为Graphics Layer准备的)。每一个Render Layer都属于他祖先中最近的那个Graphics Layer。固然根元素HTML本身要提高为Graphics Layer。
Render Layer提高为Graphics Layer的状况:
- 3D 或透视变换(perspective、transform) CSS 属性
- 使用加速视频解码的 元素
- 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素
- 混合插件(如 Flash)
- 对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition(须要是 active 的 animation 或者 transition,当 animation 或者 transition 效果未开始或结束后,提高合成层也会失效)
- will-change 设置为 opacity、transform、top、left、bottom、right(其中 top、left 等须要设置明确的定位属性,如 relative 等)
- 拥有加速 CSS 过滤器的元素
- 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
- ….. 全部状况的详细列表参见淘宝fed文章:无线性能优化:Composite
3D transform、will-change设置为 opacity、transform等 以及 包含opacity、transform的CSS过渡和动画 这3个常常遇到的提高合成层的状况请重点记住。
另外除了上述直接致使Render Layer提高为Graphics Layer,还有下面这种由于B被提高,致使A也被隐式提高的状况,详见此文: GPU Animation: Doing It Right
每一个合成层Graphics Layer 都拥有一个 Graphics Context,Graphics Context 会为该Layer开辟一段位图,也就意味着每一个Graphics Layer都拥有一个位图。Graphics Layer负责将本身的Render Layer及其子代所包含的Render Object绘制到位图里。而后将位图做为纹理交给GPU。因此如今GPU收到了HTML元素的Graphics Layer的纹理,也可能还收到某些由于有3d transform之类属性而提高为Graphics Layer的元素的纹理。
如今GPU须要对多层纹理进行合成(composite),同时GPU在纹理合成时对于每一层纹理均可以指定不一样的合成参数,从而实现对纹理进行transform、mask、opacity等等操做以后再合成,并且GPU对于这个过程是底层硬件加速的,性能很好。最终,纹理合成为一幅内容最终draw到屏幕上。
因此在元素存在transform、opacity等属性的css animation或者css transition时,动画处理会很高效,这些属性在动画中不须要重绘,只须要从新合成便可。
上述分层后合并的过程能够用一张图来描述:
blink和webkit引擎内部都是使用了两个进程来搞定JS执行、页面渲染之类的核心任务。
能够看到Compositor Thread是一个很核心的东西,后面的俩线程都是由他主要进行控制的。
同时,用户输入是直接进入Compositor Thread的,一方面在那些不须要执行JS或者没有CSS动画、不重绘等的场景时,能够直接对用户输入进行处理和响应,而Main Thread是有很复杂的任务流程的。这使得浏览器能够快速响应用户的滚动、打字等等输入,彻底不用进主线程。这里也有一个很是重要的点,后文会说。
再者,即便你注册了UI交互的回调,进了主线程,或者主线程很卡,可是由于Compositor Thread在他外面拦着,因此Compositor Thread依然能够直接负责将下一帧输出到页面上,所以即便你的主线程可能执行着高耗任务,超过16ms,可是你在滚动页面时浏览器仍是能作出响应的(同步AJAX等特殊任务除外),因此好比你有一个比较卡的动画(动画的预先计算过程或者重绘过程超过16ms每帧),可是你滚动页面是很是流畅的,也就是动画卡而滚动不卡(随便给你个demo本身试试看)。
通常咱们在devtools的Timeline里大概会看到以下过程:
也就是JS执行后触发重绘重排等操做。这里着重分析背后的运行过程,即下面这副图:
图里后半部分有两处commit,分别是主线程通知Main Thread能够执行光栅化了,以及光栅化完成、纹理生成完毕,Compositor Thread通知GPU Thread能够将纹理按照指定的参数draw到屏幕上。
Input event handlers
以前Compositor Thread接收到的用户UI交互输入在这一刻会被传入给主线程,触发相关event的回调。
All input event handlers (touchmove, scroll, click) should fire first, once per frame, but that’s not necessarily the case; a scheduler makes best-effort attempts, the success of which varies between Operating Systems.
这意味着,尽管Compositor Thread能在16ms内接收到OS传来的屡次输入,可是触发相应事件、传入到主线程被JS感知倒是每帧一次,甚至可能低于每帧一次。也就是说touchmove、mousemove等事件最快也就每帧执行一次,因此自带了相对于动画的节流效果!若是你的主线程有动画之类的卡了一点,事件触发频率很是可能低于16ms。我在最开始关于渲染时机的内容中说了scroll和resize由于和渲染处于同一轮次,因此最快也就每帧执行一次,如今来看,不只仅是scroll和resize!连touchmove、mousemove等事件,因为Compositor Thread的机制缘由,也依然如此!
详见这个jsfiddle,你们能够试试,你能够发现mousemove回调和requestAnimationFrame回调的调用频率是彻底一致的,mousemove的执行次数跟raf执行次数如出一辙,永远没有任何一次出现mousemove执行两次而rAF尚未执行一次的状况发生。另外两次执行间隔在14到20毫秒之间,主要是由于帧的间隔不会精确到16.666毫秒哈,基本是14ms~20ms之间大体波动的,你们能够打开timeline观察。另外有个挺奇怪的现象是每次鼠标从devtool移回页面区域里的时候,会很是快的触发两次mousemove(间隔有时小于5ms),虽然依然每次mousemove后依然紧跟raf,这意味着很是快速的触发了两帧。
Paint
其实Paint有两步,第一步是记录要执行哪些绘画调用,第二步才是执行这些绘画调用。第一步只是把所须要进行的操做记录序列化进一个叫作SkPicture的数据结构里:
The SkPicture is a serializable data structure that can capture and then later replay commands, similar to a display list.
这个SkPicture其实就一个列表,记录了你的commands。接下来的第二步里会将SkPicture中的操做replay出来,这里才是将这些操做真正执行:光栅化和填充进位图。主线程中和咱们在Timeline中看到的这个Paint实际上是Paint的第一步操做。第二步是后续的Rasterize步骤(见后文)。
Raster Scheduled and Rasterize
第8步生成的SkPicture records在这个阶段被执行。
SkPicture records on the compositor thread get turned into bitmaps on the GPU in one of two ways: either painted by Skia’s software rasterizer into a bitmap and uploaded to the GPU as a texture, or painted by Skia’s OpenGL backend (Ganesh) directly into textures on the GPU.
能够看出Rasterization其实有两种形式:
如今基本最新版的几大浏览器都是硬件Rasterization了,可是对于一些移动端基本仍是Software Rasterization较多。打开你的chrome浏览器输入chrome://gpu/ 能够看看你的chrome的GPU加速状况。下图是个人:
使用Hardware Rasterization的好处在于:以往Software Rasterization的方式,受限于CPU和GPU以前的上传带宽,把位图从RAM里上传到GPU的VRAM里的过程是有不可忽视的性能开销的。若Rasterization的区域较大,那么使用Software Rasterization极可能在这里出现卡顿。下面这个例子是Chrome32和Chrome41的对比,后者的版本实现了Hardware Rasterization。
不过,对于图片、canvas等状况,我没有查到究竟是怎么处理的,可是我以为绝对是有一个从CPU上传到GPU的过程的,因此应该有一些状况不是纯Hardware Rasterization的,二者应该是结合使用的。另外就是硬件仍是软件Rasterization主要仍是由设备决定的,在这个地方并无咱们手动优化的空间,可是这里涉及到一些后面的内容,因此简单介绍了一下。
commit
若是是Software Rasterization,全部tile的光栅化完成后Compositor Thread会commit通知GPU Thread,因而全部的tile的位图都会做为纹理都会被GPU Thread上传到GPU里。若是是使用GPU 的Hardware Rasterization,那么此时纹理都已经在GPU中。接下来,GPU Thread会调用平台对应的3D API(windows下是D3D,其余平台都是GL),把全部纹理绘制到最终的一个位图里,从而完成纹理的合并。
同时,很是关键的一点:在纹理的合并时,借助于3D API的相关合成参数,能够在合并前对纹理transformations(也就是以前提到的位移、旋转、缩放、alpha通道改变等等操做),先变形再合并。合并完成以后就能够将内容呈现到屏幕上了。
并非每次渲染都会执行上述11步的全部步骤,好比Layout、Paint、Rasterize、commit可能一次都没有,可是Layout又可能会不止一次。另外还有利用合成层提高来得到GPU加速的动画等相关技术的原理。接下里就是对上述步骤更加详细的分析。
重排和强制重排是老生常谈的东西了,你们也应该很是熟悉了,但在这里能够结合浏览器机制顺带讲一遍。
首先,若是你改了一个影响元素布局信息的CSS样式,好比width、height、left、top等(transform除外),那么浏览器会将当前的Layout标记为dirty,这会使得浏览器在下一帧执行上述11个步骤的时候执行Layout。由于元素的位置信息变了,将可能会致使整个网页其余元素的位置状况都发生改变,因此须要执行Layout全局从新计算每一个元素的位置。
须要注意到,浏览器是在下一帧、下一次渲染的时候才重排。并非JS执行完这一行改变样式的语句以后当即重排,因此你能够在JS语句里写100行改CSS的语句,可是只会在下一帧的时候重排一次。
若是你在当前Layout被标记为dirty的状况下,访问了offsetTop、scrollHeight等属性,那么,浏览器会当即从新Layout,计算出此时元素正确的位置信息,以保证你在JS里获取到的offsetTop、scrollHeight等是正确的。
会触发重排的属性和方法:
这一过程被称为强制重排 Force Layout,这一过程强制浏览器将原本在上述渲染流程中才执行的Layout过程前提至JS执行过程当中。前提不是问题,问题在于每次你在Layout为dirty时访问会触发重排的属性,都会Force Layout,这极大的延缓了JS的执行效率。
//Layout未dirty 访问domA.offsetWidth不会Force Layout
domA.style.width = (domA.offsetWidth + 1) + 'px'
//Layout已经dirty, Force Layout
domB.style.width = (domB.offsetWidth + 1) + 'px'
//Layout已经dirty, Force Layout
domC.style.width = (domC.offsetWidth + 1) + 'px'
复制代码
这三行代码的后两行都致使了Force Layout,Layout一次的时间视DOM数量级从几十微秒到十几毫秒不等,相比于一行JS 1微秒不到的执行时间,这个开销是难以接受的。因此也就有了读写分离、纯用变量存储等避免Force Layout的方法。不然你就会在你Timeline里看到这种10屡次Recalculate Style 和 Layout的画面了。
另外,每次重排或者强制重排后,当前Layout就再也不dirty。因此你再访问offsetWidth之类的属性,并不会再触发重排。
// Layout未dirty 访问多少次都不会触发重排
console.log(domA.offsetWidth)
console.log(domB.offsetWidth)
//Layout未dirty 访问domA.offsetWidth不会Force Layout
domA.style.width = (domA.offsetWidth + 1) + 'px'
//Layout已经dirty, Force Layout
console.log(domC.offsetWidth)
//Layout再也不dirty,不会触发重排
console.log(domA.offsetWidth)
//Layout再也不dirty,不会触发重排
console.log(domB.offsetWidth)
复制代码
重绘也是类似的,一旦你更改了某个元素的会触发重绘的样式,那么浏览器就会在下一帧的渲染步骤中进行重绘。也即一些介绍重绘机制中说的invalidating(做废),JS更改样式致使某一片区域的样式做废,从而在一下帧中重绘invalidating的区域。
可是,有一个很是关键的行为,就是:重绘是以合成层为单位的。也即 invalidating的既不是整个文档,也不是单个元素,而是这个元素所在的合成层。固然,这也是将渲染过程拆分为Paint和Compositing的初衷之一:
Since painting of the layers is decoupled from compositing, invalidating one of these layers only results in repainting the contents of that layer alone and recompositing.
两个demo几乎彻底同样,除了第二demo的.ab-right的样式里多了一行,will-change:transform;
。咱们在前文介绍合成层的时候强调过will-change: transform
会让元素强制提高为合成层。
.ab-right {
will-change: transform; //多了这行
position: absolute;
right: 0;
}
复制代码
因而在第二个demo中出现了两个合成层:HTML根元素的合成层和.ab-right所在的合成层。
而后咱们在js中修改了#target元素的样式,因而#target元素在的合成层(即HTML根元素的合成层)被重绘。在demo1中,.ab-right元素没有被提高为合成层,因而.ab-right也被重绘了。而在demo2中,.ab-right元素并无重绘。先看demo1:
明显的看到.ab-right被重绘了。
显然,demo2只重绘了HTML根元素的合成层的内容。
对了,你还能够顺便点到Raster一栏去看看Rasterization的具体过程。前面已经介绍过了,这里真正完成Paint里的操做,将内容绘制进位图或纹理中,且是分tile进行的。
先说点题外的,怎么查看合成层:
修改一些CSS属性如width、float、border、position、font-size、text-align、overflow-y等等会触发重排、重绘和合成,修改另外一些属性如color、background-color、visibility、text-decoration等等则不会触发重排,只会重绘和合成,具体属性列表请自行google。
接下来不少文章里就会说,修改opacity、transform这两个属性仅仅会触发合成,不会触发重绘和合成。因此必定要用这两个属性来实现动画,没有重绘重排,效率很高。
然而事实并非这样。
只有一个元素在被提高为合成层以后,上述状况才成立。
回到咱们以前说的渲染过程的第11步:
同时,很是关键的一点:在纹理的合并时,借助于3D API的相关合成参数,能够在合并前对纹理transformations(也就是以前提到的位移、旋转、缩放、alpha通道改变等等操做),先变形再合并。合并完成以后就能够将内容呈现到屏幕上了。
在合成多个合成层时,确实能够借助3D API的相关参数,从而直接实现合成层的transform、opacity效果。因此若是你将一个元素提高为合成层,而后用JS修改其transform或opacity 或者在 transform或opacity 上施加CSS过渡或动画,确实会避免CPU的Paint过程,由于transform和opacity能够直接基于GPU的合成参数来完成。
可是,这是在合成层总体有transform或opacity才会这么作。对于没有提高为合成层的元素,仅仅是他本身具备transform和opacity,他是做为合成层的内容。而生成合成层的内容和写进位图或纹理是在Paint和Rasterize阶段完成的,所以这个元素的transform和opacity的实现也是在Paint和Rasterize中完成的。因此仍是会重排,也就没有启用咱们常说的GPU加速的动画。
好比这个demo,一个提高为合成层的div#father和一个未提高合成层的div#child,3秒钟后JS更改child和father的transform属性。 接下来渲染的时候流程是怎样的?
因此咱们看到了,对于未提高合成层的元素,他的transform、opacity等是在主线程里Paint和配合Rasterize来实现的(其余的须要重绘的属性更是如此),依然会触发重绘,直接用JS改动这俩属性并不会得到性能提高。而若是元素已提高为合成层,那么他的transform、opacity等样式的实现就是直接由GPU Thread控制在GPU中Compositing来完成的,主线程的Composite步骤只是计算出合成的参数,耗时极小,速度极快,因此所以就有了尽可能使用transform和opacity来完成动画的经验之谈。
借用这篇文章中的例子:
div {
height: 100px;
transition: height 1s linear;
}
div:hover {
height: 200px;
}
复制代码
这段transition的实现过程是这样的:
而若是代码变成了这样
div {
transform: scale(0.5);
transition: transform 1s linear;
}
div:hover {
transform: scale(1.0);
}
复制代码
也就是Main Thread不用重排,不用重绘,Draw也不是他完成的,他的Composite步骤只是计算出具体的Compositing参数而已(示例中其实右边应该是Compositor和GPU Thread,可是做者为了简化概念、便于阐述,直接就没有提GPU Thread,你们不要在此处扣细节)。
另外,第二个例子中div为何提高为合成层,其实就是前文介绍合成层的时候说的:
对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition(须要是 active 的 animation 或者 transition,当 animation 或者 transition 效果未开始或结束后,提高合成层也会失效)
括号中的内容也很关键,元素在opacity等属性具备动画时,并非直接就提高为合成层,而是动画或者transition开始时才提高为合成层,而且结束后提高合成层也失效。
同时,元素在提高为合成层或者提高合成层失效时,会触发重绘。这也是上图一开始在动画开始前有Layout the element first time
和Paint the element into the bitmap
两步的缘由:transition开始前,div并未被提高为合成层,transition开始,div立马提高合成层,立马致使其原本所在的合成层重绘(由于要剔除掉提高为合成层的div),而且div由于提高为合成层,也立马重绘,两个重绘好的合成层Rasterize后上传至GPU中。
demo在此,因此在动画开始前看到:
在动画结束后的那一帧则是这样:
这个上述demo中,只有2个dom,因此Paint开销几乎能够忽略,可是若是是dom数量多一些,那么就极可能是下面这样了。
实时上这个状况不止是在动画和过渡时,只要一个元素被提高为合成层,在提高前和合成层失效时都会有这个过程,因此一方面是重绘带来了绘制开销,另外则是纹理上传过程由于CPU到GPU的带宽带来的上传开销(虽然如今已经有Hardware Raster不用上传,可是仍然有不能用Hardware Raster的状况,并且Hardware Raster绘制进纹理的绘制过程自己也是有开销的)。 所以处理很差就可能致使动画开始前和开始后出现一帧卡顿/延迟。
最后,重要的一点,也是通常谈到性能优化的文章中都会介绍的一点,即:
合成层提高并不是银弹。
合成层提高一方面可能会引入纹理生成、上传和重绘的开销,并且合成层提高后会占用GPU VRAM,VRAM可并不会很大。对于移动端,上述两个问题尤甚。并且在介绍合成层时,我还介绍了合成层存在隐式提高的状况。所以请合理使用。
本文主要介绍原理,因此怎么去实现16ms的动画、怎么去提高渲染性能、怎么去优化合成层数量和避免层爆炸等等、以及到底哪些状况会提高合成层、触发重绘等详细内容仍是见文末附录吧。
正文算是比较详细的介绍浏览器的渲染过程,可能须要你事先理解重绘、重排和合成,结合了一些demo,深刻了一些我以前理解错的点。
这里再次强调一下一些颠覆了我认知的内容:
三周前就第一次发布的文章终于在五一节的假期里搞定。呼….