CSS 教程 / CSS 关键帧动画:填充模式、方向与暂停
CSS 关键帧动画描述在动画时间线上的属性值,并且可以在没有悬停或聚焦触发的情况下运行。持续时间、延迟、迭代次数、填充模式和播放状态控制该行为的不同部分。
区分过渡和关键帧动画
| 标准 | 过渡 | 动画 |
|---|---|---|
| 开始条件 | 当数值因悬停、聚焦、类更改等而发生变化时。 | 在应用动画并且延迟时间结束后, |
| 状态定义 | 变化前后的 CSS 值 | 在关键帧中指定起始、中间和结束值 |
| 重复次数和方向 | 进行状态更改 | 单独指定次数、方向和停止状态 |
如果只是按钮背景色随状态改变,transition 通常已经足够。你可以查看动画效果,例如通知首次出现时的效果,或者通过多个步骤进行的可视化说明。同时用两个函数控制同一元素的同一属性会很难跟踪哪个值被应用,所以在这个练习中我们分开了角色。如果状态变化前后的效果已经足够,可以考虑使用 CSS 变换和过渡 来从简单的过渡开始。
定义关键帧和播放设置
在关键帧中,from 对应 0%,to 对应 100%。如果需要中间状态,可以添加一个进度百分比,例如 50%。仅声明关键帧名称不会使其播放;必须将其与元素的 animation-name 关联。
@keyframes travel {
0% { transform: translateX(0); }
50% { transform: translateX(3rem); }
100% { transform: translateX(6rem); }
}
.marker { animation: travel 4s linear 1s 1 normal forwards; }
这段部分代码是下面完整示例中同名关键帧和 .marker 设置的简化版本。4s 是播放一次所需的时间,1s 是启动延迟。如果未指定持续时间,默认值为 0 秒,这会使观察运动过程变得困难。最初可以通过将其分解为长格式属性来减少对时间和频率的混淆。
| 属性 | 此示例的值 | 需要更改的内容 |
|---|---|---|
animation-duration |
4s |
一次播放时间 |
animation-timing-function |
linear |
固定进度与缓动曲线的区别 |
animation-delay |
1s |
开始前的等待时间 |
animation-iteration-count |
1 |
播放次数;infinite 表示无限重复 |
animation-direction |
normal |
正向/反向/交替播放 |
animation-fill-mode |
forwards |
等待播放和播放结束后应用的样式值 |
animation-play-state |
默认 running |
用 paused 暂停当前进度 |
关键帧的百分比间隔相等,并不保证各段运动速度相同;还要考虑属性值的差异和缓动函数。本练习使用 linear,这使得检查中间位置变得容易。有关关键帧的创建和连接,请参考 MDN:@keyframes 。
比较方向、迭代次数和填充模式
normal 从开始到结束进行,而 reverse 从结束到开始进行。alternate 首次以正向播放,下次以反向播放交替进行。如果迭代次数为 2 且交替进行,则最后一次将到达第一个关键帧。
forwards 不是一个始终保持 100% 关键帧的设置。根据播放方向和次数,会保持最后实际到达的关键帧的值。在示例中,正向播放一次保持 6rem 的位置,反向播放一次保持 0 的位置,交替播放两次也保持 0 的位置。此描述基于整数次数的完整播放,分数次数可能会在中途中结束。MDN:animation-fill-mode 的方向与结束状态 。
| fill-mode | 延迟 | 完成后 |
|---|---|---|
none |
基础 CSS | 基础 CSS |
forwards |
基础 CSS | 最后播放的关键帧 |
backwards |
本次方向下的起始关键帧值 | 基础 CSS |
both |
本次方向下的起始关键帧值 | 最后播放的关键帧 |
如果基础 CSS 值和结束关键帧值相同,none 和 forwards 之间的差异可能不可见。下面的移动框默认位置为 1rem,起始为 0,结束为 6rem,以显示差异。另一方面,必读指南默认为动画移除时显示的完成状态。
运行并一次更改一个设置
将以下代码保存为 UTF-8 格式的 animation-lab.html,并在浏览器中打开。上方说明经过 400ms 入场动画后持续可见,下面的装饰框在 1 秒后开始移动,持续移动 4 秒。修改值后,保存文件并重新加载页面,从头进行比较。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Keyframes and end states exercise</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.notice { padding: 1rem; border: 1px solid #90bdd8; border-radius: .5rem; background: #eaf4fa;
opacity: 1; transform: none; animation: appear 400ms ease-out; }
@keyframes appear {
from { opacity: 0; transform: translateY(.5rem); }
to { opacity: 1; transform: translateY(0); }
}
.track { margin-block: 1rem; padding: 1rem; border: 2px dashed #8a9ba7; }
.marker { width: 2rem; height: 2rem; border-radius: .25rem; background: #166b92;
transform: translateX(1rem);
animation-name: travel; animation-duration: 4s; animation-timing-function: linear;
animation-delay: 1s; animation-iteration-count: 1; animation-direction: normal;
animation-fill-mode: forwards; }
@keyframes travel {
0% { transform: translateX(0); background-color: #166b92; }
50% { transform: translateX(3rem); background-color: #6a3989; }
100% { transform: translateX(6rem); background-color: #28633b; }
}
#pause:checked ~ .track .marker { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
.notice, .marker { animation: none; }
}
</style>
</head>
<body>
<main>
<h1>Check entrance and movement</h1>
<p class="notice">This notice remains readable without animation.</p>
<h2>Comparing end states</h2>
<p>Movement starts one second after loading. Refresh the page to compare again.</p>
<input id="pause" type="checkbox"><label for="pause">Pause movement</label>
<div class="track"><div class="marker" aria-hidden="true"></div></div>
<p>The colors and movement are decorative. Essential information remains available independently of the animation, as in this sentence.</p>
</main>
</body>
</html>
- 在默认设置下,盒子在延迟期间位置为1rem,开始时移动到0,并在结束时保持在位置6rem。检查延迟值与起始值之间的差异。
- 只将
animation-fill-mode更改为none并刷新。完成后,它将返回默认的1rem位置和默认的蓝色。恢复到 forwards。 - 只更改 fill-mode 为
both。即使在延迟期间,起始位置0也适用。确认后,返回 forwards。 - 保持 animation-iteration-count 为 1并将方向更改为
reverse。完成后,检查0位置是否保持不变,并恢复为 normal。 - 通过将 animation-direction 改为 alternate,并保持次数为 1来检查结果。在该状态下,如果将数字更改为2,将会进行第二次播放。确认后,计数为1,方向恢复为 normal。
- 刷新后立即勾选 Pause movement 复选框并稍等片刻。如果取消,将从中断的地方继续。这不同于从头重新播放的功能。
保持内容可见并减少运动
本例在减少动态效果条件匹配时,通过 animation: none 移除说明和装饰框的动画。说明仍然可读,因为默认设置是 opacity: 1;下方装饰框则回到默认的 1rem 位置。仅在结束关键帧或 forwards 中设置可读状态可能导致在没有动画的情况下内容消失。
在 DevTools 渲染面板或操作系统中启用减少动画,并重新加载。两个元素在内容仍可见时应保持静止。请参考 MDN:prefers-reduced-motion,然后在比较后恢复模拟设置。还要遵循 使用宽度、缩放和键盘测试检查响应式 CSS,以在禁用动画时检查裁剪和可用性。
在添加无限重复之前,先决定为什么需要运动以及它将如何停止。此示例使用有限的迭代,并独立于运动和颜色提供重要信息。如果没有任何运动,请检查动画名称、持续时间和减少运动的条件。对于意外的结束状态,请比较方向、迭代次数、填充模式以及底层 CSS。




