Web Animation API 实践

Web Animation API 的出现也有好几年了,它的目标是集合 CSS3 动画的性能、JavaScript 的灵活,同时将尽可能多的动画控制交由原生浏览器实现。提供一种使用 JS 描述 DOM 元素动画的通用方式。

1、引言

最近开发的项目里有涉及到动画的部分,且数量和复杂度都不低,如果用 CSS 的方式处理确实可行,但 CSS 动画没有直接暴露的时间方法,需要自己去封装后抓取结束的回调。这时候就考虑有没有一种 JS 控制的适合复杂逻辑动画的东西,于是发现了 Web Animation API 这个能使用 JS 直接操纵浏览器动画引擎的方法。

2、用法和案例对比

举个例子,如下面代码的语法:animate 方法接收两个参数:keyframesoptions ,其中 keyframes 对应CSS3中@keyframes中的声明块,options 对应 animation-* 属性及属性值。webAnimation 为动画的 Animation 对象,包含多种方法:

const element = document.querySelector('.animate');
const webAnimation = element.animate(keyframes, options);
复制代码

下面介绍 keyframesoptions 两个入参和 animate 方法。

keyframes 参数

第一个参数 keyframes 的意思是关键帧,通常是个对象数组,每个对象都是动画中的一帧。用于指定一个动画在关键节点的样式变更过程的时间轴。比如下面就是一个简单的 keyframes 的示例。

const keyframes = [
    { opacity: 1 },
    { opacity: 0 }
];
复制代码

kkh-3-1

如上图所示,动画效果是将一个元素的透明度从 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 }
}
复制代码

kkh-3-3

扫描二维码关注公众号,回复: 13165131 查看本文章

可以看到动画是变速的这么一个效果,在 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 中有些相同,又似乎有点不同。有一些属性例如 delayduration 加一个 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 网站可以查阅到比较详细的资料:

image-20210624184848168

可以看到,API 原生的兼容还是比较差的,在多个浏览器支持的版本要不是偏高,就是不能完全支持,特别是在移动端。这当然不是我们所愿意看见的,官方针对这个问题也提供针对兼容性问题的 Polyfill 库 ,可通过 npm 直接安装,也可引入如下的 js 文件:

<script src="https://cdn.jsdelivr.net/web-animations/latest/web-animations.min.js"></script>
复制代码

加上 Polyfill 之后,可以稍微对兼容性问题少点担心了。

性能和对比

根据我查阅的资料,CSS3 和 Web Animation API 在大多数情况下性能差距不是特别明显。但针对个别属性下,两者会各有优势,比如有 transformopacity 属性的时候,Web Animation API 会调用浏览器主线程以外的线程去渲染该动画,在性能上会更有优势。同时逻辑繁杂,带一些随机处理的动画也更适合用 JS 方式实现。

4、总结

我在开发中对 CSS 动画的感受很明显,比如某些值需要动态改变时会很不方便,同一个动画效果分散在不同的文件内,十分不利于维护,计时器的时间不够精确等。当然这些痛点可能是我学艺不精,还待不断探索。

平时依赖第三方的库开发是很多人的选择,但在某些对性能体积有要求的项目或对自身技术有点追求的时,再依赖引入的库会显得不太合适。当需要一种 JS 原生的解决方案时,这时候自然轮到 Web Animation API 登场了。

猜你喜欢

转载自juejin.im/post/7016912271167193118