Web Animation API 的出现也有好几年了,它的目标是集合 CSS3 动画的性能、JavaScript 的灵活,同时将尽可能多的动画控制交由原生浏览器实现。提供一种使用 JS 描述 DOM 元素动画的通用方式。
1、引言
最近开发的项目里有涉及到动画的部分,且数量和复杂度都不低,如果用 CSS 的方式处理确实可行,但 CSS 动画没有直接暴露的时间方法,需要自己去封装后抓取结束的回调。这时候就考虑有没有一种 JS 控制的适合复杂逻辑动画的东西,于是发现了 Web Animation API 这个能使用 JS 直接操纵浏览器动画引擎的方法。
2、用法和案例对比
举个例子,如下面代码的语法:animate
方法接收两个参数:keyframes
和 options
,其中 keyframes
对应CSS3中@keyframes中的声明块,options
对应 animation-*
属性及属性值。webAnimation 为动画的 Animation 对象,包含多种方法:
const element = document.querySelector('.animate');
const webAnimation = element.animate(keyframes, options);
下面介绍 keyframes
和 options
两个入参和 animate
方法。
keyframes 参数
第一个参数 keyframes
的意思是关键帧,通常是个对象数组,每个对象都是动画中的一帧。用于指定一个动画在关键节点的样式变更过程的时间轴。比如下面就是一个简单的 keyframes
的示例。
const keyframes = [
{ opacity: 1 },
{ opacity: 0 }
];
如上图所示,动画效果是将一个元素的透明度从 1 变成 0。还有一种方式是传入一个 Object 对象,对象中的每个 Key 都代表我们要设置的动画的 CSS 属性,对应的 value 属性是要进行的动画处理。数值中的每个元素都代表着动画在时间轴中的第一个时间点。
const keyframes = {
opacity: [1, 0]
}
如果用 CSS 语法创建相同效用的时间轴,则是这样:
@keyframes keyframes {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
可以看到,两者虽然语法上不同,但是写法上还是有那么一丝的相似之处。在默认情况下,时间轴上的每一个节点之间均是等时长划分。比如我们在时间轴上放 10 个节点,则每个节点的持续时间都自动等于总时长的 10%。
当然,在 CSS 中我们也经常手动指定持续的时间,去控制需要的动画时长,比如 CSS 语法创建的这种例子:
@keyframes keyframes {
0% { opacity: 1 }
10% { opacity: 0.7 }
40% { opacity: 0.3 }
70% { opacity: 0.7 }
100% { opacity: 0 }
}
可以看到动画是变速的这么一个效果,在 Web Animation API 中自然不会少了这种实现方式,系统提供了 offset
属性,属性的值支持设置从 0 到 1 之间的任意数字,等同于 CSS 语法中的百分数换算成小数。将 offset
和之前的创建形式合并就可以将上面的 CSS 时间轴这么实现:
const keyframes = [
{ opacity: 1 },
{ opacity: 0.7, offset: 0.1 },
{ opacity: 0.3, offset: 0.4 },
{ opacity: 0.7, offset: 0.7 },
{ opacity: 0 },
]
效果是基本等同的,理解上也较清晰易懂。
options 参数
.animate()
函数的 options
属性值有两种用法,第一种使用方式是直接传入一个 duration
,单位是毫秒数,比如下面这样:
element.animate([
{ opacity: 1 },
{ opacity: 0 }
], 1000);
可以看到,options
的地方传入了 1000 的毫秒数,代表在 1000 毫秒内完成该动画。这种方式适用一些偏简单的、配置不复杂的动画,可以只指定时间。
第二种方式,是 options
用更加完整的配置项传入,比如缓动方式、是否循环出现等等,下面是一个完整的配置项列表:
const options = {
// 动画执行次数
iterations: Infinity,
// 动画开始时间点
iterationStart: 0,
// 动画开始之前的延迟
delay: 0,
// 动画结束之后的延迟
endDelay: 0,
// 动画是否在下一周期逆向播放
direction: 'alternate',
// 动画时长
duration: 1000,
// 动画前后保持的状态
fill: 'forwards',
// 动画缓动类型
easing: 'ease-in-out',
}
element.animate(keyframes, options);
根据这些配置项,可以完成一个无延迟并循环播放的动画。比较熟悉动画的肯定能感觉这些属性似乎和 CSS 中有些相同,又似乎有点不同。有一些属性例如 delay
或 duration
加一个 animation-
的前缀就摇身一变成为 CSS 属性了。如下,做了一个简单的对照表,罗列了部分相似的属性:
Web Animation API 属性 | CSS属性 |
---|---|
delay | animation-delay |
duration | animation-duration |
iterations | animation-iteration-count |
direction | animation-direction |
easing | animation-timing-function |
fill | animation-fill-mode |
了解这些后,再创建一个 CSS 动画尝试用 JS 的方式去改写。如下面这个开始的延迟为一秒,动画的持续时间也是一秒,同时无限循环执行的动画,把它用 Web Animation API 可以实现完全相同的方式。
.animate-test {
animation-name: keyframes;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-delay: 1s;
}
等同于
const testOptions = {
duration: 1000,
iterations: Infinity,
direction: 'alternate',
delay: 1000
}
animate 方法
通过调用元素上的 animate
方法,并且传入 keyframes
时间轴和配置项 options
作为参数,就可以实现一个动画。而 animate
方法也提供了很多便捷的函数供我们调用来控制动画:
const element = document.querySelector('.animate');
const webAnimation = element.animate(keyframes, options);
// 动画暂停
webAnimation.pause();
// 动画开始
webAnimation.play();
除了比较基础的暂停播放等方法,还有取消动画的 .cancel()
和让动画反向播放的 .reverse()
的方法等。
之前在CSS动画中为人所诟病的动画回调比较难抓、时间不准确、需要自己做比较多的封装等问题,在这里也可以通过 Promise 的方式用 finished
事件做到比较好的解决,如下:
webAnimation.finished.then(() => {
element.remove();
})
3、注意事项
语句上的异同点
之前 .animate()
的 options
作为配置项对象,可以传入不同的配置选项或一个时间 duration
,它和熟悉的 CSS 变量相同又有点略微区别,有以下几项注意点:
- 在 CSS 中更常使用
s
也就是秒,而在 JS 中更倾向使用ms
毫秒,在 Web Animation API 中使用的也是毫秒单位。 - 如果想让动画一直循环下去,在 CSS 中会使用
animation-iteration-count
属性,值是infinite
。但是在options
中使用的是iterations
,属性值是Infinity
,要注意拼写的区别,而且Infinity
是 JavaScript 的一个关键字,不是字符串。 - CSS3 默认的缓动类型是
ease
,.animate()
默认的缓动类型是linear
线性的。
API 兼容性
兼容性也是我们在开发动画时候必须关注的一个问题,有时在开发过程中,往往因为兼容性和适配问题不得不放弃一些方法或属性,或者将原来的方式 “曲线实现”。或者引入相对应的 polyfill 进行降级。
Web Animation API 的兼容性如何呢,在 Can I use 网站可以查阅到比较详细的资料:
可以看到,API 原生的兼容还是比较差的,在多个浏览器支持的版本要不是偏高,就是不能完全支持,特别是在移动端。这当然不是我们所愿意看见的,官方针对这个问题也提供针对兼容性问题的 Polyfill 库 ,可通过 npm 直接安装,也可引入如下的 js 文件:
<script src="https://cdn.jsdelivr.net/web-animations/latest/web-animations.min.js"></script>
加上 Polyfill 之后,可以稍微对兼容性问题少点担心了。
性能和对比
根据我查阅的资料,CSS3 和 Web Animation API 在大多数情况下性能差距不是特别明显。但针对个别属性下,两者会各有优势,比如有 transform
或 opacity
属性的时候,Web Animation API 会调用浏览器主线程以外的线程去渲染该动画,在性能上会更有优势。同时逻辑繁杂,带一些随机处理的动画也更适合用 JS 方式实现。
4、总结
我在开发中对 CSS 动画的感受很明显,比如某些值需要动态改变时会很不方便,同一个动画效果分散在不同的文件内,十分不利于维护,计时器的时间不够精确等。当然这些痛点可能是我学艺不精,还待不断探索。
平时依赖第三方的库开发是很多人的选择,但在某些对性能体积有要求的项目或对自身技术有点追求的时,再依赖引入的库会显得不太合适。当需要一种 JS 原生的解决方案时,这时候自然轮到 Web Animation API 登场了。