CSS 的关键帧动画(Keyframe Animations)是 Web 开发中常用的动画方式,通过了解关键帧的语法和技巧可以让它像过渡动画一样实现复杂的动画效果。同时也能保持它作为CSS的灵活性。
本文章将介绍关键帧动画的基本语法和技巧,并介绍如何在 CSS 中使用它们。
本文中的可在线运行代码的右下角有一个
Rerun按钮,点击后可以重新运行观看代码动画效果
语法
CSS 关键帧动画使用 @keyframes 规则定义动画,其中包含一系列动画属性和关键帧。定义如下:
@keyframes fadeIn {
/* 关键帧 */
from {
opacity: 0;
}
to {
opacity: 1;
}
}
上面代码中使用 @keyframes 规则定义了一个名为 fadeIn 的关键帧,你可以将其视为一个全局变量。我们可以在 CSS 代码中的任何位置使用此关键帧动画。
这里我们定义的 fadeIn 类似于 JavaScript 中的函数,它本身不执行任何操作,我们需要使用 animation CSS 属性将关键帧应用到元素上。
.element {
animation: fadeIn 1000ms;
}
animation 至少需要两个值:
-
要应用的关键帧动画名称,在这里为
fadeIn -
动画持续时间,单位为毫秒(ms)
在上面代码中的真正意思是,我们的元素应该在 1 秒内淡入。我在 from 块中设置的任何 CSS (在本例中 opacity: 0) 都会立即生效,并且元素将平滑过渡到 to 快中设置的任何 CSS (opacity: 1)。
动画简写
animation 属性是一个"简写"属性。这意味着它可以方便地一次性设置多个属性。完整的写法如下:
.element {
animation-name: fadeIn;
animation-duration: 1000ms;
}
一般来说,使用 animation 简写更为常见,将各种值放入其中,就像将各种食材放入锅中一样。本文后面也将会使用简写的形式。
以下是一个可供你体验的实例。
多属性动画
我们可以在同一个动画声明中为多个 CSS 属性设置动画。下面是一个更酷炫的例子,它一次改变多个属性:
在上面的例子中我在关键帧里面一次性改变了两个属性(background、transform),关于 transform 后面还会继续讲解。
循环动画
默认情况下关键帧只会执行一次,但我们可以通过在动画中添加 infinite 关键字来制定他们反复运行。这对于 loading 之类的动画很有用。
它的完整格式写法是 animation-iteration-count,我们也可以指定动画的播放次数,一看情况下很少使用。
多步骤动画
在上面的例子中,我们的所有动画都是使用 from 和 to 来定义动画,如果我们需要更精确的动画或者需要两个以上步骤,我们可以使用百分比:
这里的百分比就是动画的进度,from 等于 0%,to 等于 100%。
时间函数
我们可以通过制定不同的时间函数来控制动画的"感觉"
两个方块都需要 1 秒才能完成一次完整的旋转,但它们都被赋予了不同的时间函数:第一个方块每帧移动相同的量,而第二个方块则有一定的家督和减速
交替动画
假设我们想一个元素在两个值之间来回震荡,就像闪缩的 LED 一样,我们可以将其设置为 3 步动画:
这个方法确定可以完成功能,但还有更好的方法可以达到同样的效果。我们可以指定动画的 alternate 值来使动画来回弹跳:
上面的 alternate 是 animation-direction 的简写,它可以设置为以下值:
-
normal默认,动画从 0% 开始运行到 100% -
reverse动画从 100% 开始运行到 0%,就像倒放的视频一样 -
alternate动画从 0% 开始运行到 100%,然后从 100% 开始运行到 0%,一直不停的循环
这里简单介绍一下上面的 CSS 运行过程:这个动画首先按正常方向运行,元素会在 1 秒内淡入。infinite 指定让动画重复运行。通常情况下这里重复运行会导致元素闪烁消失,然后重新淡入。但我们指定了 alternate 关键字会使动画反向运行淡出。每次动画重新运行时,它都会改变方向,就像乒乓球运动来回的在桌面上运动。
叠加动画
我们还可以对同一个元素应用多个关键帧动画:
这上面的示例中,第一个关键帧动画使元素淡入,第二个关键帧让元素一直在上下弹跳。
但是为什么不把两个关键帧动画合并为一个关键帧动画呢?那是因为我们可以为每个关键帧使用不同的动画设置。在上面的例子中,淡入是 3 秒,而每次弹跳的时间则是 500 毫秒。淡入只运行一次,而弹跳会重复运行。
animation 属性使用都好分割每组动画设置,这样我们可以组合任意多个关键帧动画。
填充模式
在前面的交替动画中,我们有一个淡出的效果
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.box {
animation: fadeOut 1000ms;
}
此动画会在 1 秒内淡出,然后闪一下,再回到原来的状态。
之所以会产生上面的行为是和动画属性的 animation-fill-mode 属性有关。当动画结束时关键帧里面的 CSS 属性都会消失,元素将会回到给他设定 class 的状态。
现在我们来看一个实际的例子:
在这个例子中元素从 0px 的位置缓慢移动到 100px 位置,然后又突然闪到 0px 位置。
要理解这个行为的核心要点是:当动画结束时,关键帧对元素的影响就消失了。元素会使用它的默认样式。
详细分解这里的步骤为:元素从 0px 位置开始缓慢移动到 100px 位置。这时由于动画已经停止关键帧对元素的影响消失,元素的 left 使用默认值 0,所以元素会立即回到 0px 处并且没有动画。所以就造成了闪烁回到起点的效果
有一种解决这个问题的方法,可以让动画结束后元素还停留在 100px 的位置应,根据上面解释只需为元素 .box 设置 left 值为 100px 即可。
当动画结束后,关键帧将不会影响 .box 元素,所以 .box 类中的 left: 100px 会生效并停留在 100px 的位置。
这样可以解决问题,但可能不是最佳的方案。
Forwards
现在我们不再依赖这种 fallback 的方案,而是考虑另一种方法,使用 animation-fill-mode:
animation-fill-mode 属性默认为 none 就像上面我们看到的动画结束时关键帧消失。
然后我们可以将其设置为 forwards 来时关键帧动画向前持续。
Backwards
有一些场景下,我们希望动画延迟一段时间再出现动画。如下面的例子所示,我希望动画在 500 毫秒后开始淡入。我们会使用 animation-delay 属性来实现这个效果。但是你会发现奇怪的问题。
观察上面的例子,元素在一开始就完全可见,经过 500 毫秒后它就会开始淡入。
这个问题和上面演示 forwards 的问题类似,关键帧中定义的 CSS 仅在动画开始时才会生效。当我们使用 animation-delay 时,我们的动画就会被推迟。意味着关键帧中的动画就不会生效。
我们可以为 .box 元素增加初始动画属性 opacity: 0。当你这样做时还会遇到另外一个问题就是当动画结束时元素又会隐藏,我们可以继续添加之前学到的 forwards 属性让元素固定在动画的最后一帧来解决这个问题。如下所示:
上面的实现方式虽能完成功能,但是很蹩脚不好理解。现在我们 backwards 属性来让动画的第一帧立即应用到元素上。试试看:
Both
学习了 forwards 和 backwards 之后,我们可以尝试一下 both 属性。它可以让开始和结束的关键帧都保留在元素上。请看例子:
观察上面的例子,opacity: 0 和 opacity: 0.5 在动画开始和结束时都会生效。
总结
这里我们学习了 @keyframes 如何定义关键帧,使用 animation 属性应用动画到元素,并且了解了 animation 的各种属性如何去控制动画的状态。
