DOM – Web Animation API
前言
以前写过相关的文章 angular2 学习笔记 ( animation 动画 )。但在项目种很少用到 Web Animation。
体会不到它的精髓,目前的感觉是,它对比 CSS Animation 更好控制,比如它有 play、pause 这些功能。
缺点就是需要把 Styles 逻辑写到 JS 里,管理可能扣点分数。
参考
MDN – Using the Web Animations API
定义
先学会用 CSS Animation 再学 Web Animation 会方便很多。
const keyframes: Keyframe[] = [ { fontSize: '16px', color: 'red', offset: 0 }, { fontSize: '32px', color: 'yellow', offset: 0.5 }, { fontSize: '48px', color: 'blue', offset: 1 }, ]; const options: KeyframeAnimationOptions = { duration: 3000, fill: 'forwards', easing: 'ease' }; const animation = h1.animate(keyframes, options);
和 CSS Animation 写法差不多. Keyframes 定义 percentage 和 Style。
注意属性要用 camelCase 哦,比如 fontSize。offset 就是 percentage,CSS 30% 这里就是 0.3。
options 负责 duration、delay、fill 等等. duration 不支持 "100s" 这种写法哦,只可以用 number 表示 millisecond。
另外,easing 的默认是 linear 这和 CSS Animation 默认 ease 不同。
当调用 .animate() 后动画就开始了。
还有一种以 style 属性出发的 keyframes 写法,它长这样:
const keyframes2: PropertyIndexedKeyframes = { fontSize: ["16px", "32px", "48px"], color: ["red", "yellow", "blue"], offset: [0, 0.5, 1], };
只是换一个写法而已,功能是完全一样的。
pause, play, reverse, cancel, finish, currentTime
pause, play, reverse, cancel 是 Web Animation 常见的操作手法。例子说明:
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <link rel="stylesheet" href="./style.css" /> <script src="./bundle.js" defer></script> </head> <body> <div class="container"> <div class="box-wrapper"> <div class="box"></div> </div> <div class="btn-wrapper"> <button class="play">Play</button> <button class="pause">Pause</button> <button class="reverse">Reverse</button> <button class="cancel">Cancel</button> </div> </div> </body> </html>
CSS Style
* { padding: 0; margin: 0; box-sizing: border-box; } body { display: grid; place-items: center; height: 100vh; .container { width: 700px; outline: 1px solid black; padding: 3rme; .box-wrapper { outline: 1px solid black; .box { width: 100px; height: 100px; background-color: pink; } } .btn-wrapper { padding-block: 1rem; display: flex; gap: 3rem; justify-content: center; button { background-color: pink; padding: 1rem; border-width: 0; cursor: pointer; display: inline-block; min-width: 128px; font-size: 1.5rem; border-radius: 4px; } } } }
JavaScript
const box = document.querySelector(".box")!;
let animation = box.animate(
[
{ offset: 0, transform: "translateX(0)" },
{ offset: 1, transform: "translateX(600px)" },
],
{
duration: 3000,
fill: "forwards",
}
);
animation.pause();
document.querySelector(".play")!.addEventListener("click", () => {
animation.play();
});
document.querySelector(".pause")!.addEventListener("click", () => {
animation.pause();
});
document.querySelector(".reverse")!.addEventListener("click", () => {
animation.reverse();
});
document.querySelector(".cancel")!.addEventListener("click", () => {
animation.cancel();
});
animation.addEventListener("finish", () => {
console.log("finish");
});
animation.addEventListener("cancel", () => {
console.log("cancel");
});
长相

Autoplay
当定义好 animation() 后, 它会立刻执行。
pause

定义好后,立刻 pause 它,可以阻止执行。
一旦调用 pause,不管 animation 执行到哪一个环节,它都会立刻停在原地。

play
play 有 2 个用法:
-
当 resume 用, 在 pause 之后调用 play, animation 就会 "继续"
-
在结束之后, 调用 play, 它有 "re-play" 的效果. (注: 如果 animation 跑到一半, 调用 play 是没有任何效果的哦. 这点和 reverse 不同)
![]()
reverse
reverse 有点小复杂。
顺序
首先它改变了 keyframes 执行的顺序,比如例子中是左到右移动,reverse 后就变成右到左。
另外,play 是依赖这个顺序的,所以当 reverse 后,play 的执行顺序也不一样了。

执行
reverse 还带有执行的功能,类似 play 那样,甚至比 play 强。任何时候调用 reverse 都会有效果 (play 在 animation 执行中调用是无效的)
-
reverse as re-play
![]()
-
reverse as resume
![]()
-
reverse in anytime
![]()
注意看一次调用 reverse, box 从左边跳到了右边, 因为一开始的方向是左到右, reverse 后变成了右到左.
cancel
一调用 cancel,element 就立刻跳回到最开始的原位。
另外 cancel 不会改变顺序,比如调用了 reverse 变成了逆序,再调用 cancel 后它依然是逆序。
finish
调用 finish 方法,animation 会直接跳到结尾的样式,就好像你突然把 duration set 成 0,直接一步到位。
currentTime
currentTime 会返回当前执行的时间 millisecond,比如说 duration 是 10 秒。
console.log(animation.currentTime);
animation 一开始的时候,如果我们 pause,这时 currentTime 会返回 0。
然后我们 play,五秒后 currentTime 会返回 5000。
11 秒后 currentTime 会返回 10000,因为 duration 是 10 秒,所以上限是 10000。
然后我们 cancel,currentTime 会返回 null。
currentTime 除了可以 get,也可以 set。
animation.currentTime = 5000;
它的效果是直接跳去 5 秒后,本来要十秒后才 finish 的,变成只需要 5 秒。
onFinish, onCancel, finished

顾名思义 animation 顺利结束就会触发 finish,如果你 pause 它就不会。resume 之后跑完了就触发。
每一次 re-play / reverse 只要是执行完 finish 都会再触发。
onCancel 只有在调用 cancel() 才会触发,而 cancel() 是不会触发 finish 的。
finished 是 onFinish 的 Promise 版,搭配 async 写起来代码比较整齐。
player.play();
await player.finished;
player.reverse();
await player.finished;
console.log('done');
不过如果要 handle cancel 写起来就未必好看了,它需要使用 try catch
player.play(); try { await player.finished; console.log('handle finished'); } catch { console.log('handle cancelled'); }
playbackRate
playbackRate 用来调整 animation 的速度,比如 define 的时候 set duration: 10000 (十秒)。
后来想加快就可以改变 playbackRate 值,比如 10 表示加快十倍,duration 就变成一秒了。
const options: KeyframeAnimationOptions = { duration: 10000, // 原本是十秒 fill: 'forwards', }; const player = box.animate(keyframes, options); player.updatePlaybackRate(10); // 加快十倍
commitStyles
fill: 'forwards' 会 lock 着相关的 styles,这不一定是我们期望的,使用 commitStyles 它会把 animation apply 到 inline styles,
这样我们就可以 cancel animation 释放 'forwards' 的 lock。
const box = document.querySelector<HTMLElement>('.box')!;
const keyframes: Keyframe[] = [{ backgroundColor: 'red' }, { transform: 'translate(100px, 200px)' }];
const options: KeyframeAnimationOptions = {
duration: 3000,
fill: 'forwards',
};
const player = box.animate(keyframes, options);
window.setTimeout(async () => {
player.play();
await player.finished; // 因为 fill 是 forwards 所以 animation styles 会保留但同时会 lock 着
player.commitStyles(); // 把 forwards 的 animation styles apply to inline styles
player.cancel(); // cancel animation unlock forwards
}, 1000);
Composite modes
参考:CSS-Tricks – Additive Animation with the Web Animations API
提醒:Composite modes 目前 IOS 还不支持哦。

有三种 composite modes:
-
replace (也是默认 mode)
const box = document.querySelector<HTMLElement>('.box')!; box.style.width = '100px'; box.animate([{ width: '100px', composite: 'replace' }], { duration: 1000, fill: 'forwards', });假设我有一个 div,width 是 100px
animate 也是 set width to 100px
结果就是什么效果也没有,最终 div 依然是 100px。
-
add
box.animate([{ width: '100px', composite: 'add' }], { duration: 1000, fill: 'forwards', // composite: 'add' // 如果全部属性都要 add mode,也可以在这里设置 });把它换成 add mode 就不同了,animation 100px 会加在原本的 100px 之上,
div 会从 100px 变成 200px,最终 width 是 200px。
-
accumulate
accumulate mode 和 add mode 类似,只有在一些特别的属性 (e.g. transform) 上能看出区别。
首先,我们有一个 box,它被 translateX 50px 然后再 rotateconst box = document.querySelector<HTMLElement>('.box')!; box.style.transform = 'translateX(50px) rotate(45deg)';效果
![]()
红框是原本的位置。
接着我们添加 animationbox.animate([{ transform: 'translateX(50px)', composite: 'add' }], { duration: 1000, fill: 'forwards', });最终 transform 是 translateX(50px) rotate(45deg) translateX(50px)
![]()
改成使用 composite: 'accumulate'box.animate([{ transform: 'translateX(50px)', composite: 'accumulate' }], { duration: 1000, fill: 'forwards', });最终 transform 是 translateX(100px) rotate(45deg)。
区别是 add 是在 transform 结尾添加 translateX(50px),而 accumulate 是累加 50px 到原本的 translateX。
我们知道 transform 是讲究顺序的,上面两句会有不同的执行效果
add 的执行是:往右边移动 50px -> 旋转 45 度 -> 再移动 50px (由于 rotate 后方向不同了,所以不再是往右边移动,而是往右下方移动)![]()
accumulate 的执行是:往右边移动 100px -> 旋转 45 度
![]()









浙公网安备 33010602011771号