MZhou's blog

CSS动画的性能优化

在Web页面中使用动画效果已经不是什么稀奇的事情了。但凡优秀的UI界面都会有一些点缀用的动画效果。举个例子,Stripe Checkout小组通过UI动画效果来增强支付体验。

图片来自@michaelvillar的Improve the payment experience with animations

可见通过UI动画来优化用户体验是一件值得去做的事情。从CSS3开始,W3C开始推出了CSS transition和animation,目前他们都处于Working Draft阶段。如果你需要的是简单的状态切换的动画,且只针对移动端来开发,那么我推荐你使用CSS动画来实现。使用CSS动画可以大大减少网页上实现动画效果的工作量,也可以避免引入大体积的JS动画库代码。

本文主要讨论的不是如何实现CSS动画,而是如果实现一个高性能的CSS动画效果。

浏览器的“硬件加速”

div {
    transform: translate3d(0, 0, 0);
}

在移动端,我们经常用到如上的CSS代码实现所谓的“硬件加速”,来提高动画的流畅度。在部分情况下,我们的CSS动画的确变的更加流畅。但这个方法并不是万能药。当页面中加速的元素越来越多时,网页的性能便会下降。为了更详细的了解原因,我们有必要了解下浏览器的内部机制。

现代浏览器大都利用了GPU来加速网页渲染。GPU是专用于图形渲染的芯片,它擅长做如下事情:

  1. 绘制位图到屏幕上
  2. 对图片进行处理,例如:修改位置、旋转和缩放等等

知道GPU擅长什么之后,让我们以Chrome为例子分析下如何利用GPU来加速页面渲染的。众所周知,Chrome的特性之一是多进程,这样任何一个页面崩溃也不会影响到其他页面。每个页面标签都有一个独立的Render进程。Render进程中包含了主线程和合成线程。主线程负责:

合成线程则主要负责:

因为现在页面中通常都有很重的Javascript和CSS,所以主线程几乎一直是满负荷运作。如果主线程一直在运行,那么页面就会卡住了。至此我们可以得到一个大概的浏览器线程模型:

我们可以将页面绘制的过程分为三个部分:layout、paint和合成。layout负责计算DOM元素的布局关系,paint负责将DOM元素绘制成位图,合成则负责将位图发送给GPU绘制到屏幕上(如果有transform、opacity等属性则通知GPU做处理)。

那么所谓的translate3d硬件加速到底做了什么事情呢?在Chrome中当某个DOM元素开启硬件加速之后,浏览器会为此元素单独创建一个“层”。当有单独的层之后,此元素的repaint操作将只需要更新自己,不用影响到别人。你可以将其理解为局部更新。所以开启了硬件加速的动画会变得流畅很多。

每个层都有自己对应上下文对象、位图,而创建上下文对象和更新位图又需要消耗内存。故当一个页面上有太多层需要更新的时候,页面往往会崩溃掉。你可以在Chrome中启用chrome://flags/#composited-layer-borders,然后在开发工具中勾选Show composited layer borders。这样就可以在页面中看到层了。可以使用下面这个DEMO,做测试:

DEMO

优化要点

我们已经知道了浏览器的大概机制,现在让我们看看该从哪几个点来入手优化我们的动画效果。

上面已经说道主线程经常是满负荷运作的,所以首先我们需要做的是给主线程减负。尽量把工作移动到合成线程(GPU)去完成。layout和paint操作都在主线程中完成,故我们需要减少动画中这两种操作。

很幸运前人已经总结了哪些CSS属性会触发layout和paint,详见CSS triggers。我们需要尽量使用transform、opacity这类不触发layout和paint操作的CSS属性。或许你已经在不知不觉中使用了这种优化,比如使用transform:translate(10px, 10px);替代position:absolute;top:10px;left:10px;

GPU虽然擅长处理图像,但是它也有瓶颈。连接CPU和GPU之间的带宽是有限的,如果一次更新的层太多,则很容易就达到GPU的瓶颈,影响到动画的流畅度。所以我们需要控制层的数量和层paint的次数。

控制层的数量可以理解,因为层的创建和更新都会消耗内存。而控制层paint的次数,是为了减少位图更新的次数。每次位图更新,合成线程就需要提交新的位图给GPU。频繁地更新位图也会拖慢GPU的效率。

或许你也可能已经在不知不觉中使用了这项优化。通常在移动端做无限滚动列表的时候,我们会复用移除可视区域的列表项。只更新列表项中的数据,然后作为新增的列表项进入用户的视野。这样便可以固定层的数量。

虽然限制很多、能使用的样式比较少,但是只要开动我们的大脑还是可以冒出很多令人惊讶的idea的。比如这个双层叠加模拟颜色变化的方案。

总结

为了得到更流畅的CSS动画效果,你需要尽量做到如下条件:

  1. 动画中尽量少使用能触发layout和paint的CSS属性,使用更低耗的transformopacity等属性
  2. 尽量减少或者固定层的数量,不要在动画过程中创建层
  3. 尽量减少层的更新(paint)次数

当然这些标准不是一定的,你需要做的是了解浏览器的机制,针对实际项目的情况来取舍。同时灵活运用手头的工具检查页面的性能,例如Chrome、Browser-perf等等。

MZhou's blog - Taste of life.

zmmbreeze / @zhoumm