大多数设备的刷新频率是60次/秒,也就是1秒钟的动画是由60个画面连在一块儿生成的,因此要求浏览器对每一帧画面的渲染工做要在16ms内完成。当渲染时间超出16ms时,1秒钟内少于60个画面生成,就会有不连贯、卡顿的感受,影响用户体验。css
一个页面帧在客户端的渲染分为如下几步:
web
JavaScript
:JavaScript实现动画效果,DOM操做等。Style(样式计算)
:确认每一个DOM元素应用的CSS样式规则。Layout(布局)
:计算每一个DOM元素最终在屏幕上的大小和位置。因为DOM元素的布局是相对的,因此当某个元素发生变化影响了布局时,其余元素也会随之变化,则须要回退从新渲染,这个过程称之为reflow。Paint(绘制)
:在多个层上绘制DOM元素的文字、颜色、图像、边框和阴影等。Composite(Render Layer合并)
:按照合理的顺序合并图层并显示到屏幕上。 浏览器在实际渲染页面的时候须要通过一系列的映射,由HTML页面构建出来的DOM树到最终的图层,映射过程以下图(来源:参考[3])所示(注意下图类名在后续有所更改,RenderObject->LayoutObject,RenderLayer->PaintLayer):Node->RenderObject:DOM树的每一个Node都有一个对应的RenderObject(一对一关系,RenderObject包含了Node的内容);ajax
RenderObject -> RenderLayer:一个或多个RenderObject对应一个RenderLayer(多对一),RenderLayer用于保证元素之间的层级关系,通常来讲位于同一位置的且层级相同的元素位于同一个Render Layer,只有某些特殊的RenderObject会专门建立一个新的渲染层,其余的RenderObject与第一个拥有RenderLayer的祖先元素共用一个。常见的生成RenderLayer的RenderObject拥有如下的一种特征参考[3]:chrome
RenderLayer -> GraphicsLayer:一个或多个RenderLayer对应一个GraphicsLayer(多对一),某些被认为是Compositing Layer的RenderLayer单独对应一个GraphicsLayer,其余RenderLayer与第一个拥有GraphicsLayer的祖先元素共用一个GraphicsLayer。每一个GraphicsLayer有一个GraphicsContext用于绘制其对应的RenderLayers,合成器将GraphicsContexts的位图合成,最终显示到屏幕上。渲染层提高为合成层的缘由以下:canvas
在网上能够看到不少的优化方案总结,大佬们都写的很好。windows
Talk is cheap. Show me the code.浏览器
结合页面渲染流程,这里将结合一些测试代码,分析动画的各类优化方案和效果:性能优化
JavaScript
:优化JavaScript的执行效率
requestAnimationFrame
代替setTimeout
和setInterval
Web Workers
中Style
:下降样式计算复杂度和范围
Layout
:避免大规模、复杂的布局
Paint/Composite
:GPU加速
JavaScript
:优化JavaScript的执行效率requestAnimationFrame
代替setTimeout
和setInterval
为何setTimeout
和setInterval
很差?
因为js是单线程执行,因此为了防止某个任务执行时间过长而致使进程阻塞,js中存在异步队列的概念,对于如setTimeout
和ajax
请求都是把进程放到了异步队列中,当主进程为空时才执行异步队列中的任务。因此 setTimeout
和setInterval
没法保证回调函数的执行时机,可能会在一帧以内执行屡次致使屡次页面渲染,浪费CPU资源甚至产生卡顿,或者是在一帧即将结束时执行致使从新渲染,出现掉帧的状况。
requestAnimationFrame
是怎么优化的?服务器
优化效果具体如何?DEMO
经过chrome的performance面板查看具体表现的差异。
经过setTimeout
进行了3次渲染,并且有长时间帧出现:
网络
requestAnimationFrame
DOM操做部分合并,只进行了2次渲染,长时间帧也被优化:
Web Worker
中Web Worker
的好处是什么?
JavaScript是单线程的,若是频繁的进行耗时操做(如实时更新数据),就会形成拥堵,影响用户交互体验。Web Worker
的做用在于为JavaScript建立了多线程环境,worker线程在后台运行,受主线程控制,二者互不干扰。worker线程负担高延迟且UI无关的任务,主线程负责UI交互就会相对流畅。
须要注意
Web Worker
没法操做DOM,本质上只是将数据刷新和页面渲染拆开执行。Web Worker
遵循同源策略且限制本地访问。优化效果具体如何?DEMO
能够经过chrome的performance面板查看具体表现的差异: 不使用web worker
,减小了一次网络请求,可是出现了长时间帧,有卡帧的风险。
web worker
以后,耗时操做无关的任务再也不被阻塞,可是增长了网络延迟。若是在项目中使用worker,初始化时间须要好好斟酌。
可考虑的应用场景
Style
:下降样式计算复杂度和范围下降样式选择器的复杂度是经常被提出的一个优化方法,实际上这个方法的效果比较微弱,根据Ivan Curic的文章[5]的测试方法(DEMO),在一个拥有50000个节点的页面中,不一样选择器复杂度对于性能的影响不会超过20ms,而通常状况下,页面的节点数都不会达到这个数量。
优化效果微弱的缘由在于浏览器引擎对选择器速度进行了优化,不一样引擎的性能优化方案不一样,因此开发者的优化是否有效是难以预测的,至少对于静态元素的优化性价比是极低的。
经过测试能够确认的一点是,应当减小伪类选择器和过长的选择器的使用。推荐按照如OOCSS、BEM等命名规范来组织CSS,优势是在微弱优化性能的同时也提升了代码可维护性。
这一点是针对较早的浏览器而言,较早的浏览器如改变了body
元素上的一个类,则其子元素都须要从新计算样式。
现代浏览器都进行了优化,因此优化效果要视具体应用场景而言。目前还没有挖掘到应用例子,后期若有发现回来填坑。
Layout
:避免大规模、复杂的布局不一样的属性致使的渲染成本不尽相同,这一点在css动画时对比尤为明显。触发layout或者paint的动画属性尤为消耗性能,因此应当尽可能使用transform
和opacity
做为动画属性,若是没法实现则考虑采用JavaScript实现动画。
性能差异有多大? 以width和transform为例,分别实现动画的性能差异:DEMO
经过width实现动画,帧率较低且曲线抖动明显,右下角也给出了一帧的渲染过程,触发了样式计算,布局,绘制和渲染层合并:
经常使用的经典布局方案有基于浮动的布局、基于绝对定位的布局,flexbox布局相较而言更加高效。在能用flexbox布局的项目中,尽可能用flexbox布局。如下DEMO尝试用三种布局方式渲染同样的界面效果来测试性能:
绝对布局:对于每个元素都须要惟一的定位坐标,当元素较多时,CSS文件偏大,致使在样式计算上花费了较多的时间。
什么是强制同步布局?
前面提到了页面渲染流程是JavaScript->Style->Layout->Paint->Composite,强制同步布局就是强制浏览器在执行JavaScript脚本前先执行布局。
什么状况会致使强制同步布局?
JavaScript运行时,获取到的元素属性样式都是上一帧的数值,因此若是在当前帧的渲染流程中,获取当前帧的某个元素属性以前对该元素进行了修改,浏览器就必须先应用属性再执行JavaScript逻辑,简而言之就是DOM先写后读操做,尤为是连续的读写操做,对浏览器的性能影响更大。 对性能影响有多大?DEMO
DEMO经过改变1000个节点的属性,测试强制同步布局事件对性能的影响,具体参照下图。能够发现性能的损耗是极大的,连续的读写操做致使连续的强制同步事件触发,JavaScript执行时间变得很长:
Paint/Composite
:GPU加速注:可在Chrome的开发者工具的layers面板查看合成层,layers面板打开方法command+shift+p(mac)/ctrl+shift+p(windows) -> show layers 将复杂/频繁变化的元素提高到合成层,这样的好处是该元素绘制的时候不会触发其余元素的绘制。渲染层提高为合成层的缘由以下(注意如下缘由是在渲染层的基础之上):
为何会有性能提高?
性能提高有多少? DEMO 经过demo能够看到,提高为合成层以后,paint所需的时间大大减小。
提高合成层是否是越多越好?
能够看到提高合成层后,paint时间大大降低。可是合成层的建立须要消耗额外的内存和管理资源,过多的合成层给页面带来的内存开销很大,DEMO建立了5000个元素,所有元素都提高为合成层与不提高时的内存消耗进行对比。这一点在移动端尤为须要注意,相比较于PC,移动设备的内存资源更加紧张。
只提高动画元素的渲染层
基于提高为合成层来提高性能的原理,当页面其余部分绘制比较复杂且相对静态时,咱们能够考虑将动画元素单独提高为合成层,减小动画元素对页面其余元素的影响。
回顾一下提高为合成层的最后一个缘由:兄弟元素是compositing layer,与当前的非composting layer有重叠,composting layer的层级低于非composting layer层。
这种状况下致使的提高合成层通常都是预期外的。其缘由与屏幕的渲染流程有关,咱们回忆一下页面映射的最后一步,每个Compositing Layer对应一张位图,合成器最后将这些位图根据层级关系合并起来最终输出到屏幕。此时咱们假设A是已知的合成层,而B理想中应当是普通渲染层,其层级关系如图所示:
scrollsWithRespectToSquashingLayer
:渲染层相对于压缩层滚动,当滚动的渲染层与合成层重叠时,会有新的合成层生成且没法压缩。DEMO(这个例子不是很好,codepen用iframe嵌入,整个iframe都变成了合成层,若是想看效果能够在本地看)squashingSparsityExceeded
:渲染层压缩后会致使压缩层过于稀疏。DEMOsquashingClippingContainerMismatch
:渲染层和压缩层的裁剪容器(clip container)不一样,简单理解就是重叠的渲染层的容器overflow类型不一样。DEMOsquashingOpacityAncestorMismatch
:渲染层与压缩层的继承自祖先的opacity属性不一样。DEMOsquashingTransformAncestorMismatch
:渲染层与压缩层的继承自祖先的transform不一样。DEMOsquashingFilterAncestorMismatch
:渲染层与压缩层的继承自祖先的filter属性不一样,或者是渲染层自己有filter属性。DEMOsquashingWouldBreakPaintOrder
:没法在不打乱渲染顺序的前提下压缩(e.g. 父元素有mask/filter属性,子元素与压缩层overlap,则假如合并了,父元素的mask/filter属性没法局部应用在压缩层,致使渲染结果有误)。DEMOsquashingVideoIsDisallowed
:video元素没法被压缩。DEMOsquashedLayerClipsCompositingDescendants
:当合成层是被剪切的子元素时,与之重叠的渲染层没法被压缩。DEMOsquashingLayoutPartIsDisallowed
:没法压缩frame/iframe/plugin。squashingReflectionDisallowed
:没法压缩有reflection属性的渲染层。 DEMOsquashingBlendingDisallowed
:没法压缩有blend mode属性的渲染层。DEMOsquashingNearestFixedPositionMismatch
:渲染层的最近fixed元素与压缩层不一样,没法被压缩。DEMO当发现页面明明没有什么内容却比较卡的时候能够检查一下是否是这个缘由,如下给出常见的层压缩解决不了的状况:
transform
动画的元素,其后的元素为relative/absolute
定位squashingClippingContainerMismatch
,渲染层与合成层的裁剪容器不一样,致使没法层压缩,出现过多的合成层。 解决方法:为动画的元素设置z-index
扰乱compositing layer的排序。DEMO本文结构主要参照文章[1],对其中的一些优化点进行了实际测试和扩展,也算是一篇读后感吧~
关于层压缩部分状况过于复杂,没找到什么资料,感受尚未彻底吃透,后面有机会再从新整理一下。感恩如下大佬!
撒花完结~欢迎指教~