在移动端,和Native相比,H5一直都被人吐槽性能差,尤为是在动画方面。
谈到整个Web app的生命周期,通常分为四个部分:javascript
通常状况下,首屏加载的时间应该小于1s
,而响应用户行为的时间应该小于100ms
,动画应该达到60fps
。这篇文章只针对动画60fps的优化。css
动画性能高,从直观上来看是动画没有抖动和卡顿,从数字上是渲染达到了60fps。60fps就是每秒60帧,因此每帧的时间只有1000ms/60=16.67ms。实际上,浏览器在每一帧还要作一些额外的事情,因此若是要达成60fps,咱们须要保证每一帧的时间在10ms到12ms之间。java
咱们先来看看浏览器在每一帧渲染都作了哪些事情。chrome
javascript
现阶段CSS动画接口比较有限,许多复杂的动画若是css不能完成,就须要使用requestAnimationFrame
函数,这样每帧都会有JavaScript的计算。浏览器
Style
当一个新的样式应用到Dom上的时候,就会引发Style的计算,同JavaScript,若是开发者使用CSS Animation,CSS Transition,那么浏览器只有在动画开始以前会作Sytle的计算,而requireAnimationFrame会在每帧都计算。多线程
Layout
当新的CSS样式出发了Layout,好比修改了width,height,pisition,这时浏览器须要从新Layout受到影响的元素,大部分状况下,即便一个位置很远的元素发生很小的宽度改变,也会引发整个document的layout,这在动画里是一个性能很是低的实现,应该尽可能避免。app
Paint
Layout变化后,受到影响的元素会从新Paint,若是遇到首次加载图片,浏览器须要将图片先解码放入内存(Image Decode)。Painting是在多个Surface上进行的,最后会出来多个Layer。函数
Composite
最后一步Composite就是把已经Paint好的各个Layer合成到一块儿。浏览器会将每一个层分红多个Tiles去Paint,可是这些做为H5开发者来讲是不可以控制的,这些层和Tiles信息会被传到GPU,最终由GPU负责渲染并显示在屏幕上。工具
并非每一个CSS的变更引发的渲染过程都要经历以上所有,实际有三条路能够走。
第一条路,好比修改元素的width,这些步骤都会执行。
第二条路:修改元素的background-color:不会有布局的变化,不会触发Layout,可是会发生Paint
第三条路:修改元素的transform,不会触发Layout,也不会触发Paint
当咱们要完成一个动画时,可能有不少种实现的方式,好比让一个小球向右平移100px,咱们能够用如下这些方法:布局
.move{left:100px}; .move{margin-left:100px}; .move{padding-left:100px}; .move{transform:translateX(100px)};
问题就在这里,使用哪一种方式性能最高?
从上面看出,若是使用不触发Layout和Paint的CSS属性完成动画,性能是最高的。那么哪些CSS属性不会触发
Layout和Paint呢?
推荐一个网站CSS TRIGGERS
,这里详细的列出了全部CSS属性在修改后是否触发Layout和Paint,是个很是给力的工具!
调试动画就要用到timeline,这里列出3个比较实用的功能。
使用requestAnimationFrame
:
前面提到,若是动画性能达到60fps,那么每一帧的时间是16ms,浏览器会有一些额外的工做,因此要保证全部的事情在10ms到12ms完成,那么留给JavaScript的时间在3ms到4ms比较合适。
大多数比较早期的Web动画是用setTimeout/setInterval这两个函数实现的,包括著名的JQuery,那个年代也没有其它的API能够用。这种实现方式性能很是低效,由于JavaScript在处理setTimeout/setInterval时,不会和渲染流程联系起来,颇有可能在一帧的中间忽然执行JavaScript致使Sytle和Layout等后面的处理要从新进行而不能在16ms内完成所有。
可是如今不一样了,咱们有了requestAnimationFrame,使用这个函数,动画每一帧在什么时候执行,如何执行,都由浏览器去决定,开发者只要把每次执行的工做写好就能够了。
function animate(){ //do something change here,like: //x+=0.1;element.style.opacity=x; requestAnimationFrame(animate); } requestAnimationFrame(animate);
查看耗时较长的js,必要时使用WebWorker
若是在动画执行过程当中,有额外的大量JavaScript计算,为了避免影响动画,建议将大量计算的JavaScript放入WebWorker。WebWorker是个功能有限的实现JavaScript多线程的工具,Worker和主线程经过message事件和postMessage方法通信,API能够参考WebWorker。
micro Optimization
V8引擎已经能够帮助咱们处理这些细小的优化。可是做为一种良好的编码习惯,仍是要保持。
Style优化
浏览器花多长时间处理Style的计算取决于有多少元素受到了影响,因此改变1000个元素的Style所耗费的时间,是改变10个元素的100倍。有了这个结论,咱们在作每一个动画时,要确保每次改变CSS时,所影响的元素都是咱们须要改变的,不要改变和动画无关的元素。
避免Layput Thrashing
什么是 Layout Thrashing?先看一个例子:
for(var i=0;i<elements.length;i++){ var blockWidth=baseElement.Width; elements[i].style.width=blockWidth; }
这是一个性能很低的例子。
当开发者设置了一个CSS样式,浏览器会继续等待看有没有更多的样式改变,而后在接下来的一帧来个批处理。
咱们再看上面那个性能低的例子,设置好一个新的样式,接着让浏览器去读一个Layout,这样浏览器就不能等待多个样式的批处理,而是强制让它先把刚才的样式作渲染,这样一读一写地循环下去,性能是很是低的,叫作“Forced Synchonous Layout”。
这个例子修改起来很是简单,只要把读Layout的事情放在循环外就行了。
var blockWidth=baseElement.Width; for(var i=0;i<elements.length;i++){ elements[i].style.width=blockWidth; }
Painting和Composite优化
Painting是很是耗性能的,因此为了不Painting,咱们通常会把这个元素变成一个Layer,当它成为一个单独Layer后,就会独立出来,当在它后面的其余元素须要重绘时,它也不受到影响,只是最后须要作Composite合成。可是并非Layer越多越好,这样会加剧Composite的工做,因此开发者须要在Paint和Composite上找到一个平衡点。
如何让一个元素变成Layer呢?目前有几种Hack方式:
.layer{transform:translate3d(0,0,0)} .layer{transform:translateZ(0)}
Chrome提供了一种更好的方式:使用will-change,和上面不一样,will-change不会直接把元素变为Layer,而是给浏览器一个提示,这可让浏览器本身决定是否成为Layer,推荐这种方式:
.layer{will-change:transform;}
推荐一篇Chrome官方的文章GPU Accelerated Compositing in Chrome,这里面很是详细地讲述了从RenderObject到RenderLayer,到GraphicsLayer,最后变成Chrome Compositor Layers的一系列过程,很是详细,文章里提到的GraphicsLayer就是本文的Layer。
上面分别从JavaScript, Style, Paint的过程介绍了一些针对性的优化方法,经过使用Chrome的DevTools,开发者能够针对渲染路径上的每个步骤优化,相信必定会有很是好的效果。
我将这些优化点简单总结为一个图: