一个例子看懂 CSS 动画
动画由两部分组成:描述步骤的 @keyframes,以及把这些步骤连同时长应用到元素上的 animation 属性。下面这张卡片会在页面加载时滑入并淡入:
修改代码(或者把时长改成 2s),预览就会重新加载,你可以再看一次卡片进场的效果。@media (prefers-reduced-motion: reduce) 代码块只对系统设置了减少动态效果的读者生效,把关键帧替换为单纯的淡入。
@keyframes 语法
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.08); }
100% { transform: scale(1); }
}
from等同于0%,to等同于100%。- 百分比步骤想加多少就加多少。样式相同的步骤可以写在同一行:
0%, 100% { ... }。 - 如果某个属性在第一个或最后一个关键帧中没有出现,它会从元素自身的值开始动画,或者以元素自身的值结束。
- 名字(
pulse)就是传给animation-name的值。它区分大小写,而且不能是none这样的关键字。
animation 属性
| 属性 | 设置什么 | 默认值 |
|---|---|---|
animation-name | 运行哪一个 @keyframes | none |
animation-duration | 一个周期的时长 | 0s |
animation-timing-function | 关键帧之间的速度曲线 | ease |
animation-delay | 第一个周期开始前的等待时间(负值表示从中途开始) | 0s |
animation-iteration-count | 周期数,或 infinite | 1 |
animation-direction | normal、reverse、alternate、alternate-reverse | normal |
animation-fill-mode | 动画前后的样式:none、forwards、backwards、both | none |
animation-play-state | running 或 paused | running |
简写几乎可以按任意顺序书写。唯一的规则是:第一个时间值是时长,第二个是延迟:
.loader {
animation: pulse 1.2s ease-in-out 0.2s infinite alternate both;
/* name duration timing delay count direction fill */
}
direction 和 fill-mode 实时演示
选择一个方向、一种填充模式和一个次数,然后点击 Play。方块会移动 200px 到轨道末端。动画结束后,下面的读数会告诉你它停在了哪里:
注意观察:
fill-mode: none(默认值):最后一帧之后,方块跳回 x = 0,并重新变成琥珀色。forwards:方块停在最后一个关键帧上。配合normal时是 x = 200 和绿色;配合reverse时停在 x = 0 和蓝色,因为最后播放的关键帧是from。alternate配合次数 2 会去了再回来,所以即使用forwards也会停在 x = 0。
无限循环:加载动画
infinite 会一直重复,而给每个子元素设置不同的 animation-delay 可以让它们错开,依次运动。循环动画要保持平缓:慢一点、幅度小一点、不要闪烁。
role="status" 加上 aria-label 会告诉屏幕阅读器用户有内容正在加载,而光靠几个圆点是表达不出这一点的。如果想做一个旋转的圆圈,请看 rotate 页面。
暂停和控制动画
animation-play-state: paused 会把动画冻结在当前位置,running 则从那里继续。把鼠标悬停在下面的条上就能暂停它:
在 JavaScript 中,element.getAnimations() 会返回正在运行的 CSS 动画,每个动画都有 pause()、play()、cancel(),以及一个可以读取或设置的 currentTime。非无限循环的动画结束时会触发 animationend 事件,每个周期结束时会触发 animationiteration 事件。
尊重减少动态效果,绝不闪烁
有两条无障碍规则适用于所有动画:
- 减少动态效果。 有些人会因为画面运动而感到不适,因此可以在系统中要求减少动态效果。最稳妥的做法是:只有在他们没有要求减少动态效果时才添加动画:
@media (prefers-reduced-motion: no-preference) {
.hero-title { animation: slide-in 0.8s ease-out both; }
}
- 不要闪烁。 一秒内闪烁超过三次的内容可能诱发癫痫发作。完全避免快速、高对比度的闪烁。
让动画保持流畅
尽量对 transform 和 opacity 做动画。浏览器应用它们时不需要重新排版页面,所以即使在性能较差的手机上也能保持流畅。对 width、height、top、left 或 margin 做动画,每一帧都会重新计算布局。要移动元素,请使用 transform 页面中介绍的 translate()。
常见错误
- 没有时长。
animation: fade;没有任何效果,因为默认时长是0s。 - 名字对不上。
animation-name必须与@keyframes的名字完全一致,包括大小写。 - 结束时跳回原样。 加上
forwards(或both)来保持最后一个关键帧。 - 延迟动画开始前闪一下。 在
animation-delay期间,元素显示的是它原本的样式。backwards(或both)会在延迟期间应用第一个关键帧。 - 通过设置相同的值来重新播放动画。 再次设置同样的
animation不会有任何效果。要先移除它,强制一次重排(el.offsetWidth),再重新设置,就像上面的 Play 按钮那样。 - 对
display做动画。 它会瞬间切换。改为对opacity和transform做动画。
常见问题
如何用 CSS 制作动画?
先用 @keyframes name { from { ... } to { ... } } 定义步骤,然后通过 animation 属性加上时长把它应用到元素上:animation: name 1s ease-out;。时长是必需的,因为它的默认值是 0s。
animation-fill-mode: forwards 有什么作用?
它让元素在动画结束后保持最后一个关键帧的样式。没有它,元素会跳回自己原本的样式。backwards 会在延迟期间应用第一个关键帧,both 则两者兼有。
如何让 CSS 动画无限循环?
设置 animation-iteration-count: infinite,或者在简写中加上 infinite:animation: spin 1s linear infinite;。再加上 alternate,每个周期就会先正向播放、再反向播放。
CSS 动画和过渡有什么区别?
过渡(transition)在属性值改变时,在两个值之间产生动画,通常由悬停或类名变化触发。动画(animation)一旦应用就会自动播放,可以有任意数量的关键帧,还可以重复、反向和暂停。
如何暂停 CSS 动画?
在元素上设置 animation-play-state: paused,比如放在 :hover 中,或者通过 JavaScript 添加的类来设置。把它改回 running,动画就会从停下的地方继续。
如何为偏好减少动态效果的用户关闭动画?
把动画放进 @media (prefers-reduced-motion: no-preference) { ... } 中,或者在 @media (prefers-reduced-motion: reduce) { ... } 中把它去掉。这个查询跟随用户操作系统中的“减少动态效果”设置。