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: hidden 或 transform: translateZ(0)。
🤔 如何重新触发动画?
element.classList.remove('animate')void element.offsetWidth // 触发重排element.classList.add('animate')CSS 动画为网页带来生命力。合理使用动画可以引导用户注意力、提供反馈、增加趣味性。下一篇我们将学习 CSS 变换和滤镜,它们是实现视觉效果的强大工具。