Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS动画的性能优化 #11

Open
zmmbreeze opened this issue Aug 31, 2016 · 0 comments
Open

CSS动画的性能优化 #11

zmmbreeze opened this issue Aug 31, 2016 · 0 comments

Comments

@zmmbreeze
Copy link
Owner

以前的旧文章,转移到这里

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

ttt9

图片来自@michaelvillarImprove 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)
  • 发送位图给合成线程

合成线程则主要负责:

  • 将位图发送给GPU
  • 计算页面的可见部分和即将可见部分(滚动)
  • 通知GPU绘制位图到屏幕上(draw)

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

ttt10

我们可以将页面绘制的过程分为三个部分: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<script src="http://static.jsbin.com/js/embed.js"></script>

优化要点

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

上面已经说道主线程经常是满负荷运作的,所以首先我们需要做的是给主线程减负。尽量把工作移动到合成线程(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等等。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant