做者:Ollie Williamsjavascript
在 JavaScript 有一个原生动画 API 叫 Web Animations API,在这篇文章中简称为 WAAPI。MDN 上已经有 很好的文档,并且,Dan Wilson 为此写了 一个很棒的文章系列。java
在本文中,咱们一块儿来对比一下 WAAPI 和 CSS 动画。git
尽管浏览器原生支持仍然有限,但 WAAPI 有一个全面和强大的 polyfill,使得如今就能在生产环境使用。github
一样地,能够在 Can I Use 查看浏览器兼容性数据。然而,这并无提供很好的信息来支持 WAAPI 的全部子功能。这里有一个检查工具:web
See the Pen WAAPI Browser Support Test by Dan Wilson (@danwilson) on CodePen.api
要想再没有 polyfill 的状况下体验全部功能,请使用 Firefox Nightly。数组
若是你曾经使用 jQuery 的 .animate()
,那么应该会以为 WAAPI 的基本语法看起来很熟悉。promise
var element = document.querySelector('.animate-me'); element.animate(keyframes, 1000);
animate
方法接受两个参数:关键帧和持续时间。与 jQuery 相比的优点是,不只是浏览器内置,并且性能也更好。浏览器
第一个参数,关键帧,是一个对象数组,每一个对象都是动画中的一个关键帧。看这个简单的例子:
var keyframes = [ { opacity: 0 }, { opacity: 1 } ];
第二个参数,持续时间,指的是想要动画持续多久,在上面的例子中是 1000 毫秒。接下来看一个更使人兴奋的例子。
这里有一些从 animista 拉取的 CSS 代码,被称为 slide-in-blurred-top 的入场动画。看起来很漂亮
在 实际PERF 比这个 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 动画的人来讲,一些术语跟咱们习惯的有所不一样。好处是打字会更快一些!
easing
而不是 animation-timing-function
animation-iteration-count
,而是 iterations
。若是咱们但愿动画永远重复,使用 Infinity
而不是 infinite
。有点混乱, Infinity
不带引号。Infinity
是一个 JavaScript 关键字,而其余值都是字符串。咱们来仔细看看一个选项:iterationStart
。
当我第一次碰到 iterationStart
有点困惑。为何要从指定的迭代开始,而不是只要减小迭代次数?当使用十进制数时,此选项很是有用。例如,能够将其设置为 .5
,动画将开始一半。要作一个完整的动画须要两个一半,因此若是迭代次数设置为 1,而且将 iterationStart
设置为 .5
,动画将从一半到动画结束播放,而后从动画开头开始,结束于中间!
值得注意的是,也能够将迭代次数设置为小于 1。例如:
var option = { iterations: .5, iterationStart: .5 }
这样,动画会从中间开始,一直播放到最后。
endDelay:若是要将多个动画串在一块儿,可是但愿在一个动画的结尾和后续动画的开始之间存在差距,这时 endDelay 就颇有用。这是一个有用的视频,由 Patrick Brosset 来解释。
在任何动画中,缓动都是最重要的元素之一。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', }
能够看到这两种缓动在 CodePen 上的区别:
值得注意的是 CSS 动画和 WAAPI 之间的另外一个区别:在 CSS 中 默认值是 ease,而在 WAAPI 默认是 linear。 ease 其实是 ease-in-out
的一个版本,当你想偷懒时这是一个很是好的选择。同时,线性表明致命的沉闷和无生命 - 一致的速度看起来机械和不天然。它被选为默认值,可能由于它是最中立的选项。然而,在使用 WAAPI 时,更好是使用缓动,以避免动画看起来很乏味和机械。
WAAPI 提供与 CSS 动画相同的性能改进,尽管这并不意味着必定就是平滑的动画。
但愿这个 API 的性能优化作到,使咱们能够避免使用 will-change
和 translateZ
成为可能。可是,至少在目前的浏览器实现中,这些属性在处理性能问题方面仍然是有帮助,有必要的。
可是,若是你的动画有延迟,则无需担忧使用 will-change
。web animations 规范的主要做者对 Animation for Work Slack community 提出了一些有趣的建议,但愿他不介意我在这里重复:
若是有一个正向的延迟,不须要使用
will-change
,由于浏览器将在延迟开始时进行分层,当动画启动时,它将准备就绪。
WAAPI 为咱们提供了一套已经在 CSS 中实现的 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 操做 @keyframe
并非件容易的的事。即便是从新启动动画这样简单的事,也是须要一些技巧的,就像 Chris Coyier 先前写过的那样。使用 WAAPI,咱们能够简单地使用 myAnimation.play()
,若是动画已经完成,将从一开始就重播动画,或者若是咱们暂停播放,则从中间迭代播放动画。
咱们甚至能够轻松地改变更画的速度。
myAnimation.playbackRate = 2; // speed it up myAnimation.playbackRate = .4; // use a number less than one to slow it down
这个方法将返回全部动画对象的数组,包含使用 WAAPI 定义的动画和 CSS 转换或动画。
element.getAnimations() // returns any animations or transitions applied to our element using CSS or WAAPI
若是你喜欢使用 CSS 来定义和使用动画,getAnimations()
容许 API 与 @keyframes
结合使用。你能够继续使用 CSS 进行大部分动画工做,而后在须要 API 时得到使用 API 的优点。
即便一个 DOM 元素只使用到一个动画,getAnimations()
也将始终返回一个数组。咱们使用那个单一的动画对象来处理。
var h2 = document.querySelector("h2"); var myCSSAnimation = h2.getAnimations()[0];
咱们也能够在 CSS 动画中使用 web animation API :)
myCSSAnimation.playbackRate = 4; myCSSAnimation.reverse();
不少经过 CSS 触发的事件,如今咱们已经可使用 JavaScript 代码来完成: animationstart
,animationend
,animationiteration
和 transitionend
。以前常常须要监听动画或转换的结束,以便从 DOM 中删除应用的元素。
在动画对象可使用 WAAPI 来完成 animationend
或 transitionend
作的事情:
myAnimation.onfinish = function() { element.remove(); }
WAAPI 为咱们提供了两个选择:event 和 promise。动画对象的 .finished
方法会返回一个在动画结束时的 promise。下面这段代码是上面例子的 promise 版本:
myAnimation.finished.then(() => element.remove())
咱们来看看来自 Mozilla 开发者网络中的一个稍微复杂点的例子。Promise.all
接受一个 promise 的数组,一旦全部 promise 完成才会运行回调函数。能够看出,element.getAnimations()
返回的是一个动画对象数组。咱们能够将数组中的全部动画对象 map 到每一个动画对象的 .finished
上,这样就得到须要的 promise 数组。
在这个例子中,只有在页面上的全部动画完成后,咱们的函数才能运行。
Promise.all(document.getAnimations().map(animation => animation.finished)).then(function() { // do something cool })
本文中提到的功能只是一个开始。从目前的规范和实施来看,将来会有一个很强大动画 API。