上周,我写了一篇关于如何使用CSS制作bitsofcode logo的动画效果。之后收到一些建议,尝试比较一下CSS动画和Web Animations API。所以今天写了这篇文章。
Web Animations API简介
和上周一样,我开始介绍Web Animations API。 Web Animations API为开发人员提供了一种方式,使用JavaScript可以直接操作浏览器的动画引擎。
如果你没有从示接触过Web Animations API的话,这篇文章是一篇很好的入门课程。除此之外,在站上已经有很多相关的教程,感兴趣的同学,可以点击这里进行阅读。
创建一个动画
使用Web Animations API创建一个动画,可以使用Element.animate()
函数,这个函数接受两个参数keyframes
和options
。
element.animate(keyframes, options);
keyframes
keyframes
对象代表动画事件的时间线(Timeline)。有两种方法来写这个对象。为了说明他,我们来创建一个grow
动画,这个动画的效果是把元素放大两倍。使用CSS的@keyframes
可以这样写:
@keyframes grow {
0% {
transform: none;
}
100% {
transform: scale(2);
}
}
第一种写keyframes
的方法是把它声明成一个单独的对象。对象中的每个键(key
)代表我们想要动画的CSS属性。每个键的值(values
)是一个数组,数组里的值是我们想要动画的CSS属性值。数组中的每个值代表动画时间轴中每一个点的。
const growKeyframes = {
transform: ['none', 'scale(2)'];
}
第二种写keyframes
的方法是把它写成一个数组。数组中的每一项代表时间线上的一个点,数组中的每个值是想要动画的CSS的属性和值。
const growKeyframes = [
{ transform: 'none' },
{ transform: 'scale(2)' }
]
默认情况下,每个点在时间轴上是等距的。例如,如果我们的时间轴上有五个点,那么每个点之间的动画过渡时间都是同样的,都是20%
。
如果我们想改变时间,给keyframes
写第二个值,给这个值设置offset
属性,这个属性接受0
到1
之间的数字,这个数值就是代表CSS的@keyframes
中的百分比值:
@keyframes alteredGrow {
0% { transform: none; }
10% { transform: scale(1.5); }
30% { transform: scale(1.9); }
100% { transform: scale(2); }
}
上面CSS的@keyframes
我们可以写成:
const alteredGrowKeyframes = [
{ transform: 'none' },
{ transform: 'scale(1.5)', offset: 0.1 }
{ transform: 'scale(1.9)', offset: 0.3 }
{ transform: 'scale(2)' }
]
options
animate()
函数的第二个参数是一个对象,该对象允许我们指定所有适用于动画CSS属性。总共有九个选项:
id
:一个独特的参考动画(设置或者获取动画的id名)delay
:指定延迟播放动画时间,对应CSS的animation-delay
属性duration
:指定动画持续播放时间,对应CSS的animation-duration
属性iterations
:指定动画循环播放次数,对应CSS的animation-iteration-count
属性direction
:指定在时间轴哪个方向运行动画,对应CSS的animation-direction
属性easing
:指定动画之间的转换方式,对应CSS的animation-timing-function
属性fill
:指定元素动画开始和结束值,对应CSS的animation-fill-mode
属性endDelay
:指定一个时间延迟后的动画iterationStart
:指定第n
次动画播放的开始时间
例如,让我们来调用alteredGrow
动画,使用CSS的动画属性来控制,比如动画播放持续3s
,延迟2s
开始播放,动画的播放方向alternate
,并且无限次播放:
.animated-element {
animation-name: alteredGrow;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-delay: 2s;
}
使用Web Animations API,我们可以指定同样的参数:
const alteredGrowOptions = {
duration: 3000,
iterations: Infinity,
direction: 'alternate',
delay: 2000
}
使用动画
在需要运用动画的元素上调用animate()
函数,并且给它指定keyframes
和options
参数:
const element = document.querySelector('.animated-element');
element.animate(alteredGrowKeyframes, alteredGrowOptions);
一旦函数被调用,动画就会自动播放。然后我们可以通过play()
和pause()
方法来控制动画的开始和停止:
const element = document.querySelector('.animated-element');
const myAnimation = element.animate(alteredGrowKeyframes, alteredGrowOptions);
myAnimation.pause();
myAnimation.play();
浏览器兼容性
bitsofcode Logo动画效果
下面视频录制了使用CSS动画制作的bitsofcode Logo的动画效果。
创建Timeline
回顾一下,Logo中动画步骤(Logo从o
字分开,"bitso"):
- 向左移动
- 回到中间
- 停在中间(等右边部分向右移动)
- 移到左边
- 旋转
- 慢慢增加旋转
- 回到不旋转位置
- 回到中间
基于这些步骤,左边部分可以创建一个Timeline:
基于这个时间线,Web动画中的keyframes
对象每步对应的样式如下:
const logoSectionLeftKeyframes = [
{ transform: 'none' },
{ offset: 0.125, transform: 'translateX(-15px)' },
{ offset: 0.25, transform: 'none' },
{ offset: 0.5, transform: 'none' },
{ offset: 0.625, transform: 'translateX(-15px)' },
{ offset: 0.67, transform: 'translateX(-15px) rotate(-10deg)' },
{ offset: 0.72, transform: 'translateX(-15px) rotate(-10deg)' },
{ offset: 0.82, transform: 'translateX(-15px) rotate(-15deg)' },
{ offset: 0.875, transform: 'translateX(-15px)' },
{ transform: 'none' }
];
因为需要使用offset
属性,所以我决定使用数组的格式来定义keyframes
。
设置参数
参数设置很简单,动画持续播放的时间是3s
,并且是无限次数的播放。
const logoSectionOptions = {
duration: 3000,
iterations: Infinity
};
运用动画
Web Animation API应用动画要比CSS Animation更繁琐。主要是因为Logo在鼠标悬浮或得到焦点时才有动画效果。正如我提到的,默认情况下,Web动画动行就创建了动画。
为了解决这一问题,我必须先创建动画,然后通过eventListeners
来监听动画的播放或暂停。此外,由于这个动画应用于每个字母,我不得不一次处理多个动画。下面的代码就是如何让动画执行:
// Array to store all animations
const animations = [];
function playLogoAnimation() {
animations.map((animation) => animation.play())
}
function pauseLogoAnimation() {
animations.map((animation) => {
animation.pause();
animation.currentTime = 0; // Reset animation to start state
})
}
function createLogoAnimation() {
const logoSectionLeftEls = Array.from( document.querySelectorAll('.logo-section-left') );
logoSectionLeftEls.forEach((el) => animations.push(el.animate(logoSectionLeftKeyframes, logoSectionTiming)))
// Animation for middle and right sections here …
// Immediately pause animation once created
pauseLogoAnimation();
}
createLogoAnimation();
// Event listeners to play or pause animation
const siteTitleLink = document.querySelector('.site__title a');
siteTitleLink.addEventListener('mouseover', playLogoAnimation);
siteTitleLink.addEventListener('mouseout', pauseLogoAnimation);
siteTitleLink.addEventListener('keyup', (e) => {
if ( e.keyCode === 9 ) playLogoAnimation();
});
siteTitleLink.addEventListener('keydown', (e) => {
if ( e.keyCode === 9 ) pauseLogoAnimation();
});
最后整个效果如下:
CSS Animations VS Web Animation API
和其他事情一样,肯定会被问及是否使用CSS或JavaScript动画,很大程度上取决于动画的细节。作为一般规则,CSS动画应用小的场景,与UI相关的动画,比如提示信息(tooltip)。Web Animation API应该留给更高级的效果。这就是我认为选择CSS动画或JavaScript动画的一般规则。
性能
CSS和JavaScript动画的性能主要取决于动画属性。一般来说,我建议只在动画中使用transform
或opacity
属性,因为这些动画可以在不同的线程上执行,也就是在浏览器的主线程上执行。
改变
transform
不会触发任何几何形状的变化或重绘,这是非常好的一点。这意味着这些操作可能可以人工启动GPU——CSS Triggers
自从我的动画只使用transform
属性,我在这两个方法之间无法看到任何显著的性能差异。使用Firefox的DevTools,可以测量两个动的帧率,他们是完全相同,都在60FPS,即使启用了主线程的动画。
我没办未能找到更多的方法来衡量这两个方法之间的性能。如果你知道任何更好的方法,请在下面的评论中与我们分享。
开发经验
在这种情况下,我个人发现CSS动画比使用Web Animation API更容易,这主要是因为后者额外的工作主要花在了动画的播放或暂停。如果要做一个更为复杂的动画,例如游戏,Web Animation API肯定是更好,然而,对于这个示例,我认为CSS动画是更简单的。
本文根据@Ire Aderinokun的《CSS Animations vs the Web Animations API: A Case Study》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://bitsofco.de/css-animations-vs-the-web-animations-api/。
如需转载,烦请注明出处:http://www.w3cplus.com/animation/css-animations-vs-the-web-animations-api.html