原文:http://www.zcfy.cc/article/3306
Web Animation API 是JavaScript 原生提供的的动画操作 API,本文我们会缩写为 WAAPI。具体内容可以查看MDN,Dan Wilson 也写了一系列教程推荐大家看看。
本文中我们会比较 WAAPI 和 CSS3 Animation 的异同。
浏览器兼容性
虽然目前只有极少部分的浏览器支持,但是 WAAPI 有完善的 Polyfill 方案。只要使用它,我们就可以在生产环境中毫无顾忌的使用 WAAPI 了!
一般来说,你可以使用Can I Use这个网站来查看浏览器兼容情况。不过对于 WAAPI 特性的支持情况数据它提供的并不是很全。相比之下我更推荐下面这个网站来查看 WAAPI 的兼容情况:
可以看看 Dan Wilson(@danvilson) 写的WAAPI 浏览器兼容测试页面。
如果想要不使用 Polyfill 来测试所有的特性,可以使用 Firefox Nightly 版本。
WAAPI 基础
WAAPI 的基础方法和 jQuery 的 .animate()
方法类似。
var element = document.querySelector('.animate-me');
element.animate(keyframes, 1000);
animate
方法接受两个参数:关键帧和持续时间。与jQuery不同的是,它不仅具有浏览器原生支持等优点,而且还具有更高的性能。
第一个参数关键帧必须是一个数组对象。每一个对象是一个动画关键帧。下面是一个简单的示例:
var keyframes = [
{ opacity: 0 },
{ opacity: 1 }
];
第二个参数持续时间,表示我们想要让动画持续多久。在之前的示例中我们是定义了1秒的持续时间。下面让我们看看更酷炫的示例。
使用 WAAPI 重写一个 animista 里的 CSS 动画
下面这个 CSS 动画是我从animista上借鉴过来的“从上模糊闪入”动画。看起来很有趣。
实际性能要远远好于 GIF。
这是 CSS 对应的关键帧:
0% {
transform: translateY(-1000px) scaleY(2.5) scaleX(.2);
transform-origin: 50% 0;
filter: blur(40px);
opacity: 0;
}
100% {
transform: translateY(0) scaleY(1) scaleX(1);
transform-origin: 50% 50%;
filter: blur(0);
opacity: 1;
}
将其用 WAAPI 改写的话应该是这样的:
var keyframes = [
{
transform: 'translateY(-1000px) scaleY(2.5) scaleX(.2)',
transformOrigin: '50% 0', filter: 'blur(40px)', opacity: 0
},
{
transform: 'translateY(0) scaleY(1) scaleX(1)',
transformOrigin: '50% 50%',
filter: 'blur(0)',
opacity: 1
}
];
可以看到我们只要写一些简单的关键帧就能让元素运动起来了。
`element.animate(keyframes, 700);`
为了让示例简单,我手动指定了持续时间。然而我们是可以向第二个参数传递更复杂的选项的起码我们是需要制定一个动画函数的。以下是所有支持的选项以及其对应的示例值:
var options = {
iterations: Infinity,
iterationStart: 0,
delay: 0,
endDelay: 0,
direction: 'alternate',
duration: 700,
fill: 'forwards',
easing: 'ease-out',
}
element.animate(keyframes, options);
按照示例选项使用的话,我们的动画会没有延迟立即开始,并且会进行往复无限运动
可以看看 CSS Grid(@cssgrid) 写的 WAAPI 模糊圆渐入动画示例。
令人烦恼的是,对于熟悉CSS动画的人来说,一些术语存在着差异。即使 WAAPI 有很多好处。
WAAPI 是
easing
而 CSS 中是animation-timing-function
CSS 中是
animation-iteration-count
而 WAAPI 中是iterations
。如果我们想要无限循环动画的话 CSS 是infinite
而 WAAPI 中则是Infinity
。令人困惑的是Infinity
不需要使用引号包裹。因为Infinity
是 JavaScript 的关键字,而其他的则是普通的字符串我们使用毫秒而不是秒,对许多 JavaScript 的开发人员会更友好。(在 CSS 中你也可以使用毫秒,但是很少有人这么做。)
接下来我们来详细的了解下 iterationStart
这个选项。
当我第一次用 iterationStart
的时候,它把我难住了。为什么会有从某个指定的循环次数开始动画的需求而不是直接减少循环次数呢?当你设置这个选项的值为小数的时候,它就非常有用了。比方说你可以设置成 .5
,这样动画就可以在一半的时候开始了。假设整个动画过程需要2.5秒,如果你设置了动画循环次数为1次且你设置循环起始数为 .5
,动画会从一半开始直到结束,然后又从开始处播放直至到达中间刚开始的地方!
对于循环次数不足1次的动画这个值也是有意义的,例如:
var option = {
iterations: .5,
iterationStart: .5
}
如刚才所说,动画会从中间开始直至结束。
endDelay
:如果你想要拼接多个动画但是有想要前后动画之间存在一个间隔的话可以使用该选项。下面这个视频会帮助大家理解这个参数。
缓动(Easing)
不管在何种动画方法中缓动都是非常重要的方法。WAAPI 给我们提供了两种不同的方法设置缓动函数 —— 在关键帧数组内设置或者在第二个选项参数对象中设置。
在 CSS 中,如果你设置了 animation-timing-function: ease-in-out
的话这就意味着你的动画开始时是缓入的,并且结束的时候是缓出的。事实上,缓动函数是基于关键帧来的,而不是针对整个动画。WAAPI 借由此对动画进行更细粒度的控制。
var keyframes = [
{ opacity: 0, easing: 'ease-in' },
{ opacity: 0.5, easing: 'ease-out' },
{ opacity: 1 }
]
不管是使用 CSS 还是 WAAPI,你都要注意不要给动画最后一帧设置关键帧,因为这是无效的。很多人都会犯这个错误。
但是有时候给整个动画添加缓动效果会更为直观。在 CSS 中这个需求是无解的,但是 WAAPI 为我们提供了接口实现这个功能。
var options = {
duration: 1000,
easing: 'ease-in-out',
}
你可以查看在现实里了解两种缓动设置的区别:
“同样的动画不同的缓动函数”在线DEMO, 作者 CSS GRID (@cssgrid)。
缓动 vs 线性
值得注意的是CSS动画和WAAPI之间的另一个区别:** CSS的缓动函数默认值是ease
,而WAAPI的默认值是linear
。** ease
其实是 ease-in-out
的另外一种写法,这两个本质上是一样的,如果你想偷懒的话可以直接写 ease
。不过线性动画比较单调,有规律的动画让人感觉非常机械、不自然。它被选为默认值,可能因为它是最中立的选项。然而,在使用 WAAPI 时比起使用 CSS 更需要注意缓动函数,以免你的动画看起来机械而乏味。
性能
WAAPI的动画性能和 CSS 动画实现一致,尽管这并不能说明其动画一定是平滑不卡顿的。
我之前希望 WAAPI 的性能优化可以避免像我在 CSS 中使用诸如 will-change
或者 translateZ
等 hack 方法。但是,至少在目前的浏览器实现中,这些属性在处理某些极端问题时仍然是有帮助的。
然而我们至少可以给动画设置延迟,而不需要担心使用 will-change
。在 Animation Slack工作会议上,Web Animation 规范的作者提了一些有趣的建议我再次引用一下,希望他不会介意:
如果你的确想要延迟,你不需要使用
will-change
。浏览器会对所有的初始延迟进行分层,并且当动画启动时,它将准备就绪。
WAAPI vs CSS 动画
WAAPI 为我们在 JavaScript 提供了一套在样式中已经实现的语法。然而,他们不应该被视为对手。如果我们决定坚持使用 CSS 进行动画和过渡,那么我们可以与 WAAPI 进行动画交互。
动画对象
.animate()
方法除了让我们的元素运动之外,它还有返回值。
`var myAnimation = element.animate(keyframes, options);`
在控制台中输出的动画对象
我们在控制台中可以看到它返回了一个动画对象。这为我们提供了各种各样的功能,其中一些非常直观,像myAnimation.pause()
。通过动态修改 animation-play-state
的值,我们可以通过 CSS 动画实现类似的效果,但是 WAAPI 语法比 element.style.animationPlayState ="paused"
稍微简洁。我们也可以通过 myAnimation.reverse()
反转我们的动画,这相比通过CSS属性 animation-direction
来实现又是一个小小的改进。
然而到目前为止,使用 JavaScript 操作@keyframes
都还不是简单的事情。正如 Chris Coyier 在本文中所述,即使是像重播动画这种简单的操作,我们也需要知道一些额外的处理方法。使用WAAPI,如果动画已经完成,我们可以简单地使用myAnimation.play()
从开始处重播动画,或者如果我们之前暂停了它,则从该处播放动画。
我们甚至可以轻松地改变动画的速度。
myAnimation.playbackRate = 2; // speed it up
myAnimation.playbackRate = .4; // use a number less than one to slow it down
getAnimations()
该方法会返回一个动画对象的数组包含该页面所有我们通过 WAAPI 方法定义的动画,以及 CSS animation
或者 transtion
定义的动画。
`element.getAnimations() // returns any animations or transitions applied to our element using CSS or WAAPI`
如果你更喜欢使用 CSS 动画,getAnimations()
也允许我们配合 @keyframes
使用。您可以继续使用CSS进行大部分动画工作,并在需要的时候使用其获得 Javascript API。让我们看看如何简单使用它。
即使一个 DOM 元素可能只有一个动画方法,getAnimations()
总是会返回一个数组。我们就获取到这唯一的动画对象来做些有趣的事情吧。
var h2 = document.querySelector("h2");
var myCSSAnimation = h2.getAnimations()[0];
现在我们可以在 CSS 动画的基础上使用 WAAPI 了!:)
myCSSAnimation.playbackRate = 4;
myCSSAnimation.reverse();
Promises 和事件
我们知道在 JavaScript 中我们已经可以使用一大堆 CSS 触发的事件了:animationstart
, animationend
, animationiteration
以及 transitionend
。我经常需要监听动画或者过渡的结束然后从 DOM 树中移除该动画元素。
WAAPI 如果要实现 animationend
或者 transitioned
同样的效果的话需要再次借用 animation 返回对象:
myAnimation.onfinish = function() {
element.remove();
}
WAAPI 同时支持事件和 Promise 方法。动画对象中 .finished
属性会返回一个 Promise 对象并在动画结束后执行。下面是个使用 Promise 方法的简单示例:
myAnimation.finished.then(() =>
element.remove())
让我们看看 MDN 中更全面的例子。Promise.all
接受一个 Promise 对象数组,并仅在所有的 Promise 都成功返回后才执行我们的回调函数。正如我们所见,element.getAnimations()
返回一个动画对象的数组。我们遍历该动画对象数组并对每个对象执行 .finished
返回我们需要的动画结束 Promise 数组。
在这个示例中,只有当页面中所有的动画元素结束的时候我们的方法才会被执行。
Promise.all(document.getAnimations().map(animation =>
animation.finished)).then(function() {
// do something cool
})
未来
本文提及的一些特性仅仅是一部分,目前规范的实现刚开始并且会越来越好。
英文原文:https://css-tricks.com/css-animations-vs-web-animations-api/