如何优雅地用原生 JS 写动画?相信 WAAPI 可以帮到您。
Web Animations API
Web Animations API (Web 动画 API,简称 WAAPI)可以让我们用 JavaScript 写动画并且控制动画。
这些 API 被设计成 CSS Animations 和 CSS Transitions 的接口。未来会对这些 API 做补充使其功能更强大,它是对网络上动画化的支持最有效的方式之一。
通过 Web 动画 API,我们可以将交互式动画从样式表移动到 JavaScript,将表现与行为分开。我们不再需要依赖 DOM,如将 CSS 属性和范围类写入元素来控制播放方向。与纯粹的声明式 CSS 不同,JavaScript 还允许我们动态地将属性值设置为持续时间。对于构建自定义动画库和创建交互式动画,Web 动画 API 可能是完成工作的完美工具。
简单实例:WAAPI 实现轮播图
对比其他动画实现方法
当我们谈及网页动画时,自然联想到的是 CSS3 动画、JS 动画、SVG 动画、APNG 动画等技术以及 jQuery.animate() 等动画封装库,根据实际动画内容设计去选择不同的实现方式。
然而,每个现行的动画技术都存在一定的缺点,如 CSS3 动画必须通过 JS 去获取动态改变的值,setInterval 的时间往往是不精确的而且还会卡顿,APNG 动画会带来文件体积较大的困扰,引入额外的动画封装库也并非对性能敏感的业务适用。目前情形对开发者而言,鱼和熊掌似乎不可兼得,既希望获得更强大便捷的动画控制能力,又希望性能和体验上足够流畅优雅,如果能有一种浏览器原生支持的通用的动画解决方案,那将是极好极好的呢。
W3C 提出 Web Animation API(简称 WAAPI)正缘于此,它致力于集合 CSS3 动画的性能、JavaScript 的灵活、动画库的丰富等各家所长,将尽可能多的动画控制由原生浏览器实现,并添加许多 CSS 不具备的变量、控制以及或调的选项。
浏览器支持情况
Firefox 48+ 和 Chrome 36+ 中提供了对 WAAPI 功能的支持。Webkit 和 Edge 已经将 WAAPI 移动到各自的待办事项列表中。可以在 Can I Use 上查看完整的浏览器支持情况.
WAAPI 有一个完善且强大的 polyfill 外部库,使得我们现在可以在生产环境下使用它,即便是在浏览器受限的情况下。web-animations.min.js 使 WAAPI 能在绝大部分浏览器运行。
WAAPI 核心用法
element.animate(keyframes, AnimationEffectTimingProperties);
keyframes:关键帧对象的数组
AnimationEffectTimingProperties:动画效果属性的对象
E.g.
document.getElementById("elementId").animate(
[{ transform: 'rotate(0)', color: '#000' },
{transform: 'rotate(360deg)', color: '#fff' }
],
{
duration: 3000,
iterations: Infinity
}
);
WAAPI 的基本语法和 jQuery 的 .animate() 十分相似。但 WWAPI 是浏览器原生支持的,不用引入外部库,在性能上也有很大的优势。接下来我们一步一步地学习 WAAPI 的用法。
关键帧对象的数组
使用 WAAPI,首先要做的是创建一个类似于 CSS3 @keyframes 的关键帧对象数组。
E.g. 创建一个轮播图滑动动画的关键帧对象数组
var slidingLeft = [
// 通过 margin-left 和 opacity 的渐变来实现简单滑动
{marginLeft: '0px', opacity: 1},
{opacity: 0.6, offset: 0.7},
{marginLeft: "-5rem", opacity: 1}
];
Web 动画 API 和 CSS3 动画的区别:
- WAAPI 不需要明确地告知每个关键帧出现的时刻在动画中的百分比,它将根据您给出的关键帧数量自动将动画划分为相等的部分。当我们想要明确地设置一个关键帧与其他关键帧的偏移量时,我们可以直接在对象中指定一个偏移量(offset:大小范围是 0 ~ 1)。
- WAAPI 的关键帧对象中,需要使用元素属性的驼峰写法,例如对应 CSS 的属性 “margin-Left” , WAAPI 的关键帧对象要使用 “marginLeft”。
- WAAPI 必须至少指定两个关键帧(表示动画序列的开始和结束状态)。如果您的关键帧对象数组只有一个对象成员, Element.animate() 将抛出不支持的异常报错。
动画效果属性的对象
我们还需要创建一个动画效果属性的对象 (AnimationEffectTimingProperties object)。
E.g.
var animateOptions = {
duration: 1500,
easing: 'ease-in-out',
}
需要注意的是,AnimationEffectTimingProperties 有一些专业的术语与我们熟悉的 CSS3 animation 属性有所不同。
下表是两者的对应关系。
AnimationEffectTimingProperties | CSS3 animation 属性 | 简述 |
---|---|---|
duration | animation-duration | 规定动画完成一个周期所花费的秒或毫秒。默认是 0。 |
easing | animation-timing-function | 规定动画的速度曲线。 |
delay | animation-delay | 规定动画何时开始。默认是 0。 |
iterations | animation-iteration-count | 规定动画被播放的次数。默认是 1。 |
direction | animation-direction | 规定动画是否在下一周期逆向地播放。默认是 ‘normal’。 |
fill | animation-fill-mode | 规定动画在播放之前或之后,其动画效果是否可见。 |
endDelay | 无对应属性 | 规定动画结束后的延迟时间。 |
iterationStart | 无对应属性 | 规定在迭代过程中动画的开始时间点。 |
- easing 的默认值是 linear,而 animation-timing-function 的默认值是 ease。在使用 WAAPI 时应使用恰当的 easing,以免让动画变得机械和乏味(默认的 linear 表示动画从头到尾的速度是相同的)。
- 与 animation-iteration-count 对应的是 iterations。如果你想要让动画一直重复下去,请使用 Infinity 代替 infinite。注意 Infinity 不需要使用逗号包裹,它是 JavaScript 的一个关键字,而其他值是字符串。
- 时间单位使用 ms 替代了 s,这对于写过 JavaScript 的开发者来说会更容易接受。(实际上在 CSS 动画中也可使用 ms,不过基本没人会这样用。)
- endDelay 表示动画结束后的延迟时间,与 delay 的默认值都为 0。如果要将多个动画串在一起,但是希望在一个动画的结尾和任何后续动画的开始之间存在时间间隔,就可以使用 endDlay。
- iterationStart 表示在迭代过程中动画的开始时间点。E.g. 如果 iterations 设置为 1,并且 iterationStart 设置为 0.5,动画将从中间开始播放到动画结尾,然后从动画开头开始,结束于中间。而如果将 iterations 和 iterationStart 都设置为 0.5,动画将从中间开始播放到结尾就结束了。
创建完以上所说的“关键帧对象的数组”和“动画效果属性的对象”,就可以简单地启动一个动画了。可以在文章后面查看具体实例。
element.animate(slidingLeft, animateOptions);
我们想学会如何去控制使用 WAAPI 创建的动画,仍需要了解 Animation 对象。
Animation 对象
animate 方法不仅仅能让元素使用动画,它还有自己的返回值 —— 一个 Animation 对象。通过改变这个对象的属性和调用它的方法,即可优雅地去控制这个动画。
var animationObj = element.animate(keyframes, AnimationEffectTimingProperties);
Animation 对象具有的属性:
属性名 | 意义 |
---|---|
currentTime | 动画的当前时间值,以毫秒为单位。如果缺少 timeline,则其值为 null,即表示动画不活动或尚未播放。 |
effect | 动画的目标效果。可以是基于 AnimationEffectReadOnly 的类型的效果对象,例如 KeyframeEffect 或 null。 |
finished | 只读。返回此动画完成后执行的 Promise 对象。 |
id | 标识动画的字符串。 |
playState | 只读。动画的播放状态,常见的有 running, paused, finished。 |
playbackRate | 动画的播放速度,默认值为 1。当值为 0.5 时动画会减慢一半,当值为负值时动画会反向播放 |
ready | 只读。返回当前动画准备好播放时执行的 Promise 对象。 |
startTime | 动画播放开始时的预定时间。默认为 null。可以更改此值以使动画在不同的时间开始。 |
timeline | 与当前动画相关联的时间轴。默认与文档的时间轴相同。 |
Animation 对象具有的方法:
方法名 | 用途 |
---|---|
cancel() | 清除由此动画造成的所有关键帧效果,并中止其播放。 |
finish() | 将当前播放时间设置为与当前播放方向相对应的动画结束时间。若为正常播放,则把当前播放时间设置为动画总时长;若为反向播放则把当前播放时间设置为 0。 |
pause() | 暂停播放动画 |
play() | 开始或继续播放动画。如果动画已完成,则再次开始播放动画。 |
reverse() | 将动画的播放方向反转。如果在未播放的动画上调用,动画将从结尾向开头反向播放。如果对暂停中的动画进行了调用,动画将向反方向继续播放。同样的效果可以通过设置 playbackRate *= -1; 来实现 |
Animation 对象的事件处理程序(回调函数):
WWAPI 支持使用 event 和使用 Promise 两种方式来处理事件。
下面是两种 event:
- oncancel:定义动画被取消时或执行 cancel() 时触发的回调函数。
- onfinish:定义动画自然完成播放时或执行 finish() 时触发的回调函数。
E.g. 如果一个动画被取消了,移除其元素。
animation.oncancel = animation.effect.target.remove();
Promise
Promise 对象用于一个异步操作的最终完成(或失败)及其结果值的表示。
animation 对象的 ready 和 finished 属性会返回一个 Promise 对象,分别对应在动画准备播放和播放结束的时候。下面是一个使用 Promise 来处理事件回调的示例:
myAnimation.finished.then(() =>
element.remove())
document.getAnimations() 方法返回当前有效的所有 Animation 对象的数组,此数组包括 CSS Animation,CSS Transition 和 Web Animation。
在较新版本的 Chrome 浏览器的开发者工具 (F12) 的 console drawer 中还能查看页面中所有的 Animation,包括用 CSS3 定义的动画:
WAAPI 实例 —— 轮播图
看懂了上面提到的知识,我们就可以使用 WWAPI 来实现一个简单的轮播图,点击打开 codepen 预览并查看完整代码。
JS 部分代码:
var indexBody = document.querySelector(".index_body");
var btnLeft = document.querySelector(".left_btn");
var btnRight = document.querySelector(".right_btn");
var slideBox = document.querySelector('.slidebox');
var items = slideBox.getElementsByClassName("slide_item");
// 使用 web animations 创建动画,左滑和右滑共用一个动画
var slidingLeft = [{marginLeft: '0px', opacity: 1},
{opacity: 0.6, offset: 0.7},
{marginLeft: "-5rem", opacity: 1}
];
var sliding = slideBox.animate(
slidingLeft,
{
duration: 1500,
easing: 'ease-in-out',
}
);
sliding.onfinish = function(){
slideBox.style.marginLeft = '0px';
if(sliding.playbackRate != -1){slideBox.appendChild(items[0]);
}
btnRight.onclick = slideRight;
btnLeft.onclick = slideLeft;
};
function slideRight(){
sliding.playbackRate = 1; // 切换滑动方向为右
sliding.play();
btnLeft.onclick = null;
btnRight.onclick = null;
// console.log(sliding.effect);
};
function slideLeft(){slideBox.insertBefore(items[items.length - 1],items[0]);
slideBox.style.marginLeft = '-5rem';
sliding.playbackRate = -1; // 切换滑动方向为左
sliding.play();
btnLeft.onclick = null;
btnRight.onclick = null;
};
btnRight.onclick = slideRight;
btnLeft.onclick = slideLeft;
// 自动滑动
var slideTimer = setInterval(() => {btnRight.click();
},3000);
// 鼠标悬停时停止滑动
indexBody.onmouseover = function(){clearInterval(slideTimer);
};
// 鼠标离开时继续自动滑动
indexBody.onmouseout = function(){slideTimer = setInterval(() => {btnRight.click();
},3000);
};
web-animations 官方的 demo 非常适合初接触 WAAPI 的同学作为开始的范例。
另外推荐一个挺炫酷的动画库 —— Animista,上面有很多用 CSS3 实现的动画(可在线查看代码,主要是 keyframes),我们可以尝试用 WWAPI 来实现同样的动画效果。
参考资料
- 《Using the Web Animations API》网站:MDN
- 《CSS Animation 与 Web Animation API 之争》译者:范洪春,原文:OLLIE WILLIAMS
- 《Web Animation API 从入门到上座》作者:AlloyTeam
- 《Animation》网站:MDN
- 《CSS3 animation 属性》网站:W3school