CSS Handbook · 变换与动画

第八章 / 8.3 animation 与 @keyframes

第八章 · 8.3 节

animation 与 @keyframes


transition 只能描述"从一个状态到另一个状态"的两点式过渡,而 animation 配合 @keyframes 可以定义任意数量的中间关键帧,还能无限循环、自动播放,无需任何交互触发——适合加载动画、注意力引导等场景。

与 transition 的区别

transitionanimation
触发方式需要状态变化触发(如 :hover)可自动播放,无需交互
关键帧数量仅 2 个端点任意多个(0%–100% 之间自由插入)
循环不支持支持 infinite 等循环次数控制

定义 @keyframes

@keyframes pulse {
  0%   { transform: scale(1); opacity: 1; }
  50%  { transform: scale(1.08); opacity: .85; }
  100% { transform: scale(1); opacity: 1; }
}

百分比代表动画时间轴上的进度节点,也可以用 from / to 分别代替 0% / 100%。

animation 子属性

属性作用
animation-name引用的 @keyframes 名称
animation-duration单次动画时长
animation-timing-function缓动曲线,同 transition
animation-delay开始前的延迟
animation-iteration-count播放次数,infinite 为无限循环
animation-directionnormal / alternate(往返播放)等
animation-fill-mode动画前后的状态如何呈现,见下节
.badge {
  animation: pulse 1.6s ease-in-out infinite;
}
效果预览:持续脉动

fill-mode 与初始/结束状态

默认情况下,动画播放结束后元素会"跳回"未应用动画时的原始样式;animation-fill-mode 可以让它保留在关键帧定义的状态:

取值行为
none(默认)动画前后都不应用关键帧样式
forwards播放结束后保持最后一帧的样式
backwards在 delay 等待期间就提前应用第一帧的样式
bothforwards + backwards 同时生效
.enter {
  animation: fade-up .4s ease-out both;
}
@keyframes fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

实战:加载骨架屏

用渐变背景配合 background-position 动画,是"骨架屏加载态"最经典的实现方式:

.skeleton {
  background: linear-gradient(90deg, #e7e8e1 25%, #f4f5f0 37%, #e7e8e1 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease infinite;
}
@keyframes shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0 0; }
}
效果预览

Chapter 8 · Section 8.3

animation and @keyframes


transition can only describe a two-point change from one state to another, while animation combined with @keyframes can define any number of intermediate keyframes, loop indefinitely, and play automatically with no interaction required — ideal for loading animations, attention-guiding effects, and similar scenarios.

How animation Differs from transition

transitionanimation
TriggerNeeds a state change to trigger (e.g. :hover)Can play automatically, no interaction needed
KeyframesOnly 2 endpointsAny number (insert freely between 0%–100%)
LoopingNot supportedSupports iteration control such as infinite

Defining @keyframes

@keyframes pulse {
  0%   { transform: scale(1); opacity: 1; }
  50%  { transform: scale(1.08); opacity: .85; }
  100% { transform: scale(1); opacity: 1; }
}

Percentages mark progress points on the animation timeline; from / to can also be used in place of 0% / 100%.

animation Sub-properties

PropertyPurpose
animation-nameThe @keyframes name it references
animation-durationDuration of a single iteration
animation-timing-functionEasing curve, same as transition
animation-delayDelay before it starts
animation-iteration-countNumber of plays; infinite loops forever
animation-directionnormal / alternate (plays back and forth), etc.
animation-fill-modeHow the state before and after the animation is shown, see next section
.badge {
  animation: pulse 1.6s ease-in-out infinite;
}
Live preview: continuous pulse

fill-mode and Initial/End States

By default, when an animation finishes the element snaps back to its original style as if the animation never applied; animation-fill-mode lets it keep the state defined by the keyframes:

ValueBehavior
none (default)Applies keyframe styles neither before nor after the animation
forwardsKeeps the last frame's style after the animation ends
backwardsApplies the first frame's style early, during the delay wait
bothforwards + backwards take effect together
.enter {
  animation: fade-up .4s ease-out both;
}
@keyframes fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

In Practice: Loading Skeleton Screens

Animating background-position with a gradient background is the classic way to build a skeleton loading state:

.skeleton {
  background: linear-gradient(90deg, #e7e8e1 25%, #f4f5f0 37%, #e7e8e1 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease infinite;
}
@keyframes shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0 0; }
}
Live preview