前端性能优化之更平滑的动画

想要达到平滑的动画效果,浏览器需要避免复杂繁琐的工作,比如解析代码,构建渲染树,绘制,布局等等工作。庆幸的是,我们有GPU。

从一个简单例子说起。

假设现在有这样的需求,鼠标移上图标时,图片向右移动300个像素单位,效果大概是下面这样。

smoother-animation-demo2.gif

很自然,人类的理所当然思维会指引我们进坑。

.move {
  position: absolute;
  left: 0;
  &:hover {
    left: 300px;
  }
  transition: all 2s;
}

没问题,这能完成我们的任务。
让我们打开Chrome Timeline工具Records一下。

blob.png

(注意这是我故意放大很多倍图片的效果。)

绿色部分表示浏览器在执行Paint操作,我们都知道动画的刷新频率需要保持在60fps以上肉眼才会觉得很顺畅。低于30fps则会让用户感觉到明显卡顿。而这个实验中,浏览器略感吃力。为了能够按时完成任务,浏览器选择跳过部分帧,于是我们就看到走走停停的卡顿效果,这也通常被称为丢帧。

于是我又换成padding-left,jQuery animate方法,然而并没有改善。
再试试translate…

.move {
  position: absolute;
  left: 0;
  &:hover {
    transform: translate(300px,0);
  }
  transition: all 2s;
}

blob.png

我的天!质的飞跃有没有,相同动画下还能保持这个高的刷新频率。

查查资料。

reflow,repaint

当我们在某个元素上执行动画时,浏览器需要每一帧都检测是否有元素受到影响,并调整他们的大小,位置,通常这种调整都是联动的,我们称为reflow。同样的,浏览器还需要监听元素的外观变化,通常是背景色,阴影,边框等可视元素,并进行重绘,我们称为repaint。每次reflow,repaint后浏览器还需要合并渲染层并输出到屏幕上。所有的这些都会是动画卡顿的原因。

Reflow 的成本比 Repaint 的成本高得多的多。一个结点的 Reflow 很有可能导致子结点,甚至父点以及同级结点的 Reflow 。在一些高性能的电脑上也许还没什么,但是如果 Reflow 发生在手机上,那么这个过程是延慢加载和耗电的。
——浏览器的渲染原理简介

你可以在csstrigger上查找某个css属性会触发什么事件。

CPU,GPU分工

在没有GUP的年代,所有任务都是CPU来完成。庆幸的是,这篇文章一开始就说到,我们生活在有GPU的年代。GPU擅长图形计算,这是它的强项。

一个页面上来,浏览器会经过一番处理,生成相应的位图。然后把它们扔给GPU。GPU再拼接位图,合并渲染层,并把最终结果输出到屏幕。

问题根源

《你不知道的Z-Index》中有提到,如果某个元素处于以下状态:

  • 当一个元素位于HTML文档的最外层(元素)

  • 当一个元素position不为initial,并且拥有一个z-index值(不为auto)

  • 当一个元素被设置了opacity,transforms, filters, css-regions, paged media等属性。

  • (当然还会有其他情况)

那么就会产生一个新的渲染层(堆叠上下文),这时候执行动画,只需要GPU按照现有的位图,按照相应的变换在独立的渲染层中输出,然后再合并输出。这个过程并不需要主线程CPU的参与。

而当我们使用left,padding,margin,JavaScript,jQuery等方式来执行动画,那么流程就不一样了。

还是举上面的例子,CPU需要重新计算每一帧,元素的位置,外观,重新定位元素,repaint,然后才生成位图,传给GPU渲染。

所以当我们开启GPU渲染的时候,浏览器主线程就能空出来去响应用户输入了。

注意

然而并不是所有浏览器默认都会开启GPU渲染,所以通常会用translate3d,translateZ,或者是opacity < 1等来强制开启。

请注意,GUP渲染并不是越多越好,首先它很占内存,在移动端比较耗电, 还可能会有坑。

联系前几天看了前端农民工的一篇文章《CSS3硬件加速也有坑!!!》,里面的Demo也是让我久久不能忘怀。

blob.png

文中说到如果有一个元素,它的兄弟元素在复合层中渲染,而这个兄弟元素的z-index比较小,那么这个元素(不管是不是应用了硬件加速样式)也会被放到复合层中。

暂时还不是很懂,兄弟元素如何定义。特别是看了源码后稍稍有些困惑,div > h1 与 ul > li为何成为兄弟元素?

所以我

无耻地求div.io邀请码一枚。

动画优化要点总结

  • 执行动画尽量使用CSS3 keyframes和 trainsition

  • 如果需要JS执行动画,使用requestAnimationFrame,或者Velocity,避免使用jQuery动画,setTimeout,setInterval。

  • js动画的优点是,我们能随时控制开始,暂停,停止,而CSS不行。缺点是没办法像css这样优化,因为js动画是在主线程上跑的。

  • 动画尽量使用transform,opacity,尽量避免left/padding/background-position等

  • 尽量避免不必要的动画发生(废话)点击这里

  • 尽可能的为产生动画的元素使用fixed或absolute的position

  • 阴影渐显动画尽量用伪类的opacity来实现。点击这里

  • 使用3D硬件加速提升动画性能时,最好给元素增加一个z-index属性,人为干扰复合层的排序,可以有效减少chrome创建不必要的复合层,提升渲染性能,移动端优化效果尤为明显。(来自前端农民工)

  • 使用Chrome Timeline工具检查

  • 时刻把浏览器处理流程记在心里

关于排错 (12月15)

TimeLine面板非常有用,如果你遇到很棘手的性能问题,肉眼看不错哪里有问题,你可以试试把有嫌疑的元素加个display:none !important;。也可以使用Source面板设置断点找茬。

TQ的H5动感影集性能分析非常棒!

推荐阅读

原文出处:http://w3ctrain.com/2015/12/15/smoother-animation/