重绘,回流和合成,了解基本浏览器绘制帮你优化页面性能

前端开发中,很重要的一种需求就是各种浏览器、 webView下用js或者css实现动态效果。简单的例如一个toast弹窗渐渐消失,复杂的如【美团外卖】外卖 这个demo中照片轮播+缩放做的一个小相册功能。动画效果越来越多,交互也越来越复杂。随着页面一个个的特效加上去,突然页面就莫名的卡了。。。

1、repaint/reflow 重绘和回流

我们看到的页面是由dom树加上样式来确定一个个盒模型的。现代浏览器例如chrome,解析html文档后会先查看页面内的link外联css,等到css加载解析完后渲染dom树。这就是为什么许多页面性能指南有一条是css写在顶部,因为这个是早加载完了早渲染,要不页面就白屏的状态。dom树加载渲染好后,我们的工作总是要来破坏这一片和谐的景象,这个元素渐渐放大,顶部飞下来个广告等等需求。

在CSS3不那么普及的IE8时代中,我们做页面动画基本都是利用js定时器改变元素位置来做类似平移,幻灯等效果。用改变属性值的办法我们难免会触发代价高昂的repaint/reflow。

repaint,就是浏览器得知元素产生了不影响排版的情况下后对这个元素进行重新绘制的过程。例如我们改变了元素的颜色,加个下划线等。

reflow, 浏览器得知元素产生了对文档树排版有影响的样式变化,对所有受影响的dom节点进行重新排版工作

reflow的开销大于repaint,所以用marginLeft, width ,height等属性改变dom时我们要注意减少影响的范围。基本原则就是,把动画元素用position:absolute踢出文档流,这样R&R就限制在了absolute元素的子节点。告诉浏览器,我这块结构跟其他的单独渲染,不要搅和全页面了。

具体更多的关于repaint/reflow介绍,有一篇百度的博文写的很好,感兴趣的同学可以移步观摩:如何减少浏览器repaint和reflow(上)

2、compositor layer 合成渲染层

合成渲染,听着可能有些陌生,但是你肯定用过。对于transform/opacity 这两种变换,浏览器不会用repaint/reflow处理,而是在已经渲染好的元素基础上进行附加工作。例如一个黑底色的div,往右飞100px, 传统JS过程是对每次修改left值后重新画一个div。而如果我们用transform:translate(0,100px) ,transition:2s 浏览器则是把这个绘制好的div单独放在一个画面层再平移这个层过去,div的几何形状,颜色不会再重复计算,而是保留在这个图层中。Google开发者的一篇文章介绍了合成渲染的好处,其中有图描述了理想动画效果的流程

js改变样式,样式只触发合成属性,不触发 repaint/reflow.附原文链接

developers.google.com/w

如果你想进一步集中用户的显卡资源来渲染你的动画,给动画元素使用translate3d属性或者简单加上translateZ属性,让浏览器以为这是要3d变换的元素,会让浏览器单独开合成层渲染你的动画效果。集中资源的好处是动画过程会更加平滑。

在实际应用中,我总结的经验就是PC可以稍微放纵性能,大块的dom改变也不会有感知。但是对于移动端,除非特别特殊的需求,比如一个元素斜抛抛物线轨迹运行,用js相对方便,其他都要用合成渲染。

类似半个手机屏幕大的元素平移,repaint在IPhone也吃不消。

3、监控你的合成层

前面介绍了H5动画的性能技巧,但是在目前h5效果的主战场,各大手机Webview中,也不是合成层就可以随便用。

例如这个外卖h5版的右侧菜单栏,开始版本是一个iscroll把店铺内所有菜品都放在一起滚动。快餐商家只有20-30个菜品的时候表现良好,但是后来有小卖铺接入数据,一个菜单栏动辄100条数据的时候,在华为P9手机的浏览器里就发现一滑动菜单就开始闪烁+抖动。排查了repaint/reflow,触摸事件响应等bug后,试着删除菜品dom到50个以内后,药到病除~。为什么单独渲染层还会抖动呢?这是因为合成耗费的资源和变化元素的dom也是正相关。如何量化合成层的耗费?打开要检测的动画,用chrome开发者模式查看。

动画开始前,切换到timeline标签。选中2,记录绘制信息。点击1,开始录制。然后做动画操作。完成一个动画动作后再按1,停止录制。这时候能看到渲染的时间轴。我们节选一段时间的信息后,点击3部分可以选中某一时间帧。在底部就可以看到tab签多了个layers选项,就可以查看当前帧渲染的层数以及每个层对应的内存耗费。看到哪些层可以优化的,就果断减dom,减层数,减变换,达到解决卡顿的目的。

最后附上文章开头处的相册源码,GitHub - sexdevil/photoViewer 作者我本来是懒,只是缩放图片用transform scale,平移滑动等都是 marginLeft ,left 来回修改,结果跑起来以后被手机卡的教做人了,不得不把每个变换都汇总成transfrom一起渲染。源码为开发环境,实际生产环境在webpack打包后只有20k,走gzip后只有7k.

编辑于 2017-02-17

文章被以下专栏收录