Skip to content

CSS 动画

CSS 动画通过 @keyframes 定义动画序列,使用 animation 属性控制播放。与过渡不同,动画可以自动播放、循环、暂停,适合更复杂的视觉效果。

@keyframes 定义动画#

基本语法#

@keyframes slide-in {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

百分比关键帧#

@keyframes bounce {
0% {
transform: translateY(0);
}
25% {
transform: translateY(-20px);
}
50% {
transform: translateY(0);
}
75% {
transform: translateY(-10px);
}
100% {
transform: translateY(0);
}
}

多个属性#

@keyframes fade-scale {
0% {
opacity: 0;
transform: scale(0.8);
}
50% {
opacity: 1;
}
100% {
opacity: 1;
transform: scale(1);
}
}

animation 属性#

简写语法#

.element {
animation: name duration timing-function delay iteration-count direction
fill-mode play-state;
}
/* 示例 */
.box {
animation: slide-in 0.5s ease-out 0.2s 1 normal forwards running;
}

单独属性#

.box {
animation-name: slide-in;
animation-duration: 0.5s;
animation-timing-function: ease-out;
animation-delay: 0.2s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
animation-play-state: running;
}

animation-duration#

动画持续时间:

.fast {
animation-duration: 0.3s;
}
.normal {
animation-duration: 1s;
}
.slow {
animation-duration: 3s;
}

animation-timing-function#

速度曲线(同 transition):

.ease {
animation-timing-function: ease;
}
.linear {
animation-timing-function: linear;
}
.bounce {
animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

逐帧动画#

.sprite {
animation-timing-function: steps(8);
}

animation-iteration-count#

播放次数:

.once {
animation-iteration-count: 1;
}
.twice {
animation-iteration-count: 2;
}
.infinite {
animation-iteration-count: infinite;
}

animation-direction#

播放方向:

.normal {
animation-direction: normal;
} /* 正向 */
.reverse {
animation-direction: reverse;
} /* 反向 */
.alternate {
animation-direction: alternate;
} /* 正向 → 反向交替 */
.alternate-reverse {
animation-direction: alternate-reverse;
} /* 反向 → 正向交替 */

animation-fill-mode#

动画前后的样式:

.none {
animation-fill-mode: none;
} /* 默认:动画前后恢复原样 */
.forwards {
animation-fill-mode: forwards;
} /* 保持最后一帧 */
.backwards {
animation-fill-mode: backwards;
} /* 延迟期间应用第一帧 */
.both {
animation-fill-mode: both;
} /* 结合 forwards 和 backwards */

animation-play-state#

控制播放/暂停:

.running {
animation-play-state: running;
}
.paused {
animation-play-state: paused;
}
/* 悬停暂停 */
.animated:hover {
animation-play-state: paused;
}

多动画#

.element {
animation:
slide-in 0.5s ease forwards,
fade-in 0.3s ease,
pulse 2s infinite 1s;
}

实战案例#

加载动画#

/* 旋转加载器 */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* 脉冲效果 */
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.skeleton {
background: #e5e7eb;
animation: pulse 1.5s ease-in-out infinite;
}
/* 弹跳点 */
@keyframes bounce-dots {
0%,
80%,
100% {
transform: translateY(0);
}
40% {
transform: translateY(-10px);
}
}
.loading-dots span {
display: inline-block;
width: 8px;
height: 8px;
background: #3b82f6;
border-radius: 50%;
animation: bounce-dots 1.4s ease-in-out infinite;
}
.loading-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.loading-dots span:nth-child(3) {
animation-delay: 0.4s;
}

入场动画#

@keyframes fade-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-up {
animation: fade-up 0.5s ease forwards;
}
/* 交错入场 */
.stagger > * {
opacity: 0;
animation: fade-up 0.5s ease forwards;
}
.stagger > *:nth-child(1) {
animation-delay: 0.1s;
}
.stagger > *:nth-child(2) {
animation-delay: 0.2s;
}
.stagger > *:nth-child(3) {
animation-delay: 0.3s;
}
.stagger > *:nth-child(4) {
animation-delay: 0.4s;
}

注意力动画#

/* 摇晃 */
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-5px);
}
20%,
40%,
60%,
80% {
transform: translateX(5px);
}
}
.shake {
animation: shake 0.5s ease;
}
/* 心跳 */
@keyframes heartbeat {
0%,
100% {
transform: scale(1);
}
14% {
transform: scale(1.1);
}
28% {
transform: scale(1);
}
42% {
transform: scale(1.1);
}
70% {
transform: scale(1);
}
}
.heartbeat {
animation: heartbeat 1.5s ease-in-out infinite;
}
/* 闪烁 */
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.blink {
animation: blink 1s step-end infinite;
}

悬浮效果#

@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.float {
animation: float 3s ease-in-out infinite;
}
/* 光晕效果 */
@keyframes glow {
0%,
100% {
box-shadow: 0 0 5px rgba(59, 130, 246, 0.5);
}
50% {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.8);
}
}
.glow {
animation: glow 2s ease-in-out infinite;
}

进度指示#

@keyframes progress {
from {
width: 0;
}
to {
width: 100%;
}
}
.progress-bar {
height: 4px;
background: #3b82f6;
animation: progress 2s ease forwards;
}
/* 不确定进度 */
@keyframes indeterminate {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(400%);
}
}
.progress-indeterminate {
width: 25%;
height: 4px;
background: #3b82f6;
animation: indeterminate 1.5s ease-in-out infinite;
}

打字机效果#

@keyframes typing {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes cursor {
0%,
100% {
border-color: transparent;
}
50% {
border-color: currentColor;
}
}
.typewriter {
overflow: hidden;
white-space: nowrap;
border-right: 2px solid;
animation:
typing 3s steps(30) forwards,
cursor 0.75s step-end infinite;
}

动画事件#

JavaScript 可以监听动画事件:

element.addEventListener('animationstart', handleStart)
element.addEventListener('animationend', handleEnd)
element.addEventListener('animationiteration', handleIteration)

性能优化#

使用 transform 和 opacity#

/* 好:只改变 transform 和 opacity */
@keyframes good {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 避免:改变布局属性 */
@keyframes avoid {
from {
height: 0;
}
to {
height: 100px;
}
}

GPU 加速#

.gpu-accelerated {
transform: translateZ(0);
/* 或 */
will-change: transform, opacity;
}

减少动画#

@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

常见问题#

🤔 动画结束后元素消失?

使用 animation-fill-mode: forwards 保持最后一帧状态。

🤔 动画闪烁?

添加 backface-visibility: hiddentransform: translateZ(0)

🤔 如何重新触发动画?

element.classList.remove('animate')
void element.offsetWidth // 触发重排
element.classList.add('animate')

CSS 动画为网页带来生命力。合理使用动画可以引导用户注意力、提供反馈、增加趣味性。下一篇我们将学习 CSS 变换和滤镜,它们是实现视觉效果的强大工具。