动画那点事

先说说前端动画种类吧:CSS动画,JS动画,Canvas动画,SVG动画、PNG动画。 注:这里我们讨论的动画都为2D动画。

衡量动画的标准 衡量动画性能高低的是“帧数”,在动画进行过程中,每一幅静止的画面就叫一帧,所以说帧数越高,动画效果看起来越流畅。一般来说显示器的帧数是60HZ,50-60Hz是效果最好的,如果20Hz以下就会有可能感到卡顿。但是一般帧数低点没关系,毕竟硬件条件总是有限的,但是最怕丢帧,经常玩游戏的同学可能体会比较深。

#一、CSS动画 ##1.Transition属性 顾名思义Transition(过渡),从开始状态过渡到结束状态。 特点:

  • 能够很方便的制作简单的放大缩小旋转的动画,注意一个transition规则,只能定义一个属性的变化(all属性除外)。
  • 需要事件触发,且每次触发只能执行一次。
  • 只能定义开始状态和结束状态,不能定义中间状态,而且状态必须是具体数值,不能为auto或none。 ##2.Animation属性 animation相比Transition多了关键帧概念,弥补了Transition动画只有开始结束两个状态的不足。此外animation还可以定义循环播放及循环次数,动画播放方向等。 我们需要先通过 @keyframes 定义关键帧的,然后在animation属性引用关键帧的名称
div:hover {
  animation: 1s keyframesName;
}
@keyframes keyframesName {
  0% { background-color: yellow; }
  100% { background: blue; }
}

用CSS就不得不提兼容性,transition和animation都支持IE10+,总的来说还算不错了。

#二、CSS动画优化 首先回顾一下页面绘制的过程:

构造DOM模型/解析CSS规则 → 生成渲染树 → 排版/重排Layout → 绘图/重绘Painting

1486146410achieve-60-fps-mobile-animations-css3-01.png

如果动画中的CSS导致整个页面重新绘制(比如 left/top/right/bottom 属性的转换),就可能带来潜在的性能问题。对于哪些CSS属性会使浏览器生重排或重绘,不同内核有不同的结果,具体可以参考CSS Triggers

CSS动画优化主要就是靠GPU加速。当浏览器某个DOM元素开启硬件加速之后,它会为此元素单独创建一个“层”。当有单独的层之后,此元素的repaint操作将只需要更新自己,不会影响到其他元素,减小了重绘面积也就提高了动画性能。

See the Pen SVG and CSS animation performance by Neil McCallion (@njmcode) on CodePen.

观察上面例子,在Chrome中打开 Rendering 菜单,然后勾选Painting FlashLayer Border选项,触发浏览器重绘的区域就会被绿色覆盖,而浏览器创建的硬件加速层会被棕色边框圈起来。

##层创建标准

3D 或透视变换(perspective transform) CSS 属性 使用will-change 属性的元素 使用加速视频解码的元素 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素 混合插件(如 Flash) 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素 拥有加速 CSS 过滤器的元素 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里) 元素有一个 z-index 较低且有在一个复合层渲染的兄弟元素(换句话说就是该元素在复合层上面渲染,这一点需要特别注意!)

需要注意的是层的创建和更新也是消耗资源的,而且CPU和GPU之前的总线带宽也是一定的,所以太多的层也会适得其反,而且不仅仅是卡顿,还可能会将页面卡死。

##使用will-change的注意事项: will-change的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。 动画开始之前要提前添加will-change属性,动画结束时,will-change 的值要及时被更新为 auto,实际操作时我们会根据Hover事件添加will-change。 IE所有版本包括Edge都不支持will-change属性。

#三、JS动画 JS实现动画动画效果其实就是定时修改CSS属性,所以我们肯定首先想到用setTimeout,每隔16.667ms触发一次重绘逻辑。 使用setTimeout的一些问题,因为浏览器是单线程的,JS定时器的执行时间并不会很准确,所以帧数会抖动。

再考虑一个问题,如果我们设置每10ms执行一次setTimeout会怎样。 IC550966.png 动画过度绘制,之前我们提到过显示器一般为60Hz,即每 16.7ms 刷新一次,显示器在一个周期内只能显示最后一帧,在图中的方框内出现了两帧就会导致前一帧丢失。而且还会浪费 CPU 周期以及消耗额外的电能等问题。更重要的是切换到其他选项卡或浏览器已最小化时,动画依然会执行。

为了解决上述问题,W3C推出了requestAnimationFrame方法,从根本上解决了动画触发的时序控制。

See the Pen requestAnimationFrame 测试 by zhangchen (@zhangchen915) on CodePen.

从此测试结果可以看出requestAnimationFrame一般小于5000m一点,而setTimeout总是大于requestAnimationFrame用时且大于5000ms。

#四、PNG动画 你一定会问,PNG怎么可以制作动画?其实很简单,动画不是由帧构成的吗,那我在一张PNG图里放上动画所有的帧,然后像跑马灯一样切换每一帧,画面就动起来了。所以,这种动画的适用场景也非常有限,但是对于非常复杂而且面积较小的动画不失为一种高效的解决办法。 实现方法就是利用了animation-timing-function:steps()

在阿里云首页中就用到了这种动画方式 GIF.gif

动画的每一帧都是由类似下面的一张张图片做成的 ali.png

下面的例子是Twitter的“喜欢”按钮:

See the Pen Twitter heart button animation by 一丝 (@yisi) on CodePen.

可以看下SVG的实现方式,使用了第三方组件库,还需要JS控制,对于快速迭代的前端来说项目复杂度和开发时间都是必须考虑在内的,显然为一个动画显然不值得,值得一提的是这个效果还有利用box-shadows实现的纯CSS版本,读者可以自行搜索。

#五、SVG动画 SVG是使用XML文档描述来绘图,所以当内联进HTML时可以像操作DOM一样操作SVG,同时也这也说明SVG可以借助CSS和JS实现更多交互性动画,再加上SVG自身也支持一些动画属性和图形效果(如裁剪和遮罩、背景虚化模式和滤镜等等),所以SVG做出来的效果也不容小觑。 从这点来看:SVG更适合用来做动态交互,而且SVG绘图很容易编辑,只需要增加或移除相应的元素就可以了。

svg基于路径,所以非常适合描边这种的动画。

GIF.gif

主要应用: 代替字体图标、文字特效、图标动画(如loading效果)、描边动画 但需要注意的是,在安卓4.4以下对svg的支持并不好。

#六、Canvas动画 Canvas是基于像素的,只能通过脚本驱动。 之前你一定看过一些网站的背景动画,比如知乎登录页的背景,其实就是用Canvas实现的。

IC628910.png

一般显示器大小差别都不大,所以主要差别在于对象数。所以Canvas非常绘制适合复杂的动画,所以地图、图表类应用(如谷歌地图、百度Echart)都采用的Canvas实现。

关于Svg和Canvas可以去CodePen搜索,可以很直观的感受二者应用场景的区别。

#七、GIF/webP/webM动画 千万别一谈动画就想代码,代码是做出来是很厉害。 很多时候在快速的业务迭代下,我们需要在实现成本、效果和性能之间做出平衡。

如果有你是炉石玩家,并经逛官方论坛的话,你一定见过下面的效果。

这其实就是直接用的 webM 实现的,当然 webM 存在一些兼容,但是我们为不兼容的浏览器提供 mp4 格式的动画。实现方式可以直接看这里的源码。

参考阅读: Accelerated Rendering in Chrome CSS3硬件加速也有坑 will-change Web动画性能指南 CSS动画的性能优化 基于脚本的动画的计时控制(“requestAnimationFrame”) SVG 与 Canvas:如何选择