Web Animation API非常的棒。除了能很好的支持平常动画之外。我发现还可以使用Promise、rAF和CSS Transition来重新创建它们,结果类似人体工程学。
Web Animations
使用Web动画,你可以让任何元素,并让它按动画序列帧播放。
element.animate([
{transform: 'translateX(0px)', backgroundColor: 'red'},
{transform: 'translateX(100px)', backgroundColor: 'blue'},
{transform: 'translateX(50px)', backgroundColor: 'green'},
{transform: 'translateX(0px)', backgroundColor: 'red'},
//...
], {
duration: 3000,
iterations: 3,
delay: 0
}).finish.then(_ => console.log('I’m done animating!'));
很完美,对吧?而这只是涉及Web动画的冰山一角。该规范有更多的功能,如动画的合成,处理多个时间轴和事件处理。如果你想了解更多,可以查阅动画相关规范,但遗憾的是,现在只有Firefox和Chrome浏览器支持,而且仅支持部分功能。如果你想在生产中使用这个,那还是需要做一定的考虑。
DIY
然而,对于日常开发而言,要真正的像上面那样使用:用动画系列声明的方式来定义动画。需要使用Web Animations Polyfill,它包含了我们实际所需要的更多功能。
Object.assign(element.style,
{
transition: 'transform 1s, background-color 1s',
backgroundColor: 'red',
transform: 'translateX(0px)',
}
);
requestAnimationFramePromise()
.then(_ => animate(element,
{transform: 'translateX(100px)', backgroundColor: 'blue'}))
.then(_ => animate(element,
{transform: 'translateX(50px)', backgroundColor: 'green'}))
.then(_ => animate(element,
{transform: 'translateX(0px)', backgroundColor: 'red'}))
.then(_ => console.log('I’m done animating!'));
不是很好,但是在所有的浏览器中都可以工作,而且还能很好的工作,你不觉得吗?最奇怪的是,你可能需要在第一个关键帧上的定义和其他关键帧不同。如果你能使用ES2017的async/await
,那么你可以使用更少的缩进。
我是怎么实现的呢?如果你用JavaScript来讨论连锁(Chains),你必然会想到Promises。这也是我为什么要对requestAnimationFrame
和CSS Transition进行包装的原因。也许这可能会让人感到惊讶,但这的确是你所需要的。
CSS Transition包装
当你使用CSS Transition让一个元素具有一个动画效果,其实他有一个transitioned
事件:
function transitionEndPromise(element) {
return new Promise(resolve => {
element.addEventListener('transitionend', function f() {
element.removeEventListener('transitionend', f);
resolve();
});
});
}
这样使用后不会注册我们的侦听器,也不会泄露内存!有了这个,我可以使用Promises替代回调,等待动画的结束。
rAF包装
我们也可以对requestAnimationFrame
进行包装,让其变得更简单:
function requestAnimationFramePromise() {
return new Promise(resolve => requestAnimationFrame(resolve));
}
有了这个,我们可以使用Promise而不是回调来等待下一帧。
我们自己的animate()
我们可以将它合并到我们自己的element.animate()
中。
function animate(element, stylz) {
Object.assign(element.style, stylz);
return transitionEndPromise(element)
.then(_ => requestAnimationFramePromise());
}
这就是它!这就是背后执行的所有事情,让我们的代码能更好的工作。这是对animation
和transition
非常轻量的抽象处理,会给很多开发人员带来很多的便利。不要忘了Promise.all()
这样的Promise工具,它可以将多个动画合并在一起执行。这个概念可以很容易地应用到JavaScript生态系统中的构造器中。
Trip wires
显然,我需要提醒自己,事件冒泡。
这意味着,如果您在两个元素上使用这个技术,而其中一个元素是另一个元素的祖先,那么从继承者的传递结束事件将使前面的动画链向前推进。幸运的是,通过检查事件,可以很容易的解决这样的现象,比如event.target
:
function transitionEndPromise(element) {
return new Promise(resolve => {
element.addEventListener('transitionend', function f(event) {
if (event.target !== element) return;
element.removeEventListener('transitionend', f);
resolve();
});
});
}
本文根据@Surma的《DIY Web Animations: Promises + rAF + Transitions》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://dassur.ma/things/raf-promise/
如需转载,烦请注明出处:https://www.w3cplus.com/animations/raf-promise.html