正在加载,请稍候…

CSS 动画与过渡:完整指南及示例

从零学习 CSS 过渡和动画,掌握 @keyframes、动画属性、性能优化,构建流畅且无障碍的生产级动画。

CSS 动画与过渡:完整指南及示例

CSS 动画与过渡:完整指南及示例

CSS 动画和过渡让 UI 生动起来。做得好,它们能引导注意力并提升感知性能;做得差,则会分散注意力并拖慢应用。以下是如何正确使用它们。

CSS 过渡:简单的状态变化

过渡在状态变化(悬停、聚焦、激活、类名改变)之间对属性变化进行动画处理。

/* 语法:transition: property duration timing-function delay */
.button {
  background: #3b82f6;
  transform: translateY(0);
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  
  /* 对特定属性进行动画 */
  transition: background 200ms ease,
              transform 150ms ease,
              box-shadow 200ms ease;
}

.button:hover {
  background: #2563eb;
  transform: translateY(-2px);
  box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}

/* 简写:transition: all */
.button { transition: all 200ms ease; }
/* ⚠️ 避免在生产中使用 transition: all —— 影响性能 */

CSS 动画与过渡:完整指南及示例 插图

时间函数

.element {
  transition-timing-function: ease;         /* 默认:慢-快-慢 */
  transition-timing-function: linear;       /* 匀速 */
  transition-timing-function: ease-in;      /* 开始慢 */
  transition-timing-function: ease-out;     /* 结束慢 ← 用于进入 UI 元素 */
  transition-timing-function: ease-in-out;  /* 两端慢 */
  
  /* 自定义 cubic-bezier */
  transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* 弹簧弹跳 */
  
  /* 类似弹簧 */
  transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

合适的过渡时长

小型 UI 反馈(悬停、激活):  100-150ms
颜色/透明度变化:              200-300ms
面板、抽屉滑入:              200-350ms
页面过渡:                    300-500ms
装饰/主视觉动画:             500ms-1s

规则:响应用户输入的过渡应感觉即时(< 200ms)
      装饰性动画可以更慢

CSS 动画:@keyframes

动画自动运行,可以循环,并且有更多控制点。

/* 定义动画 */
@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* 或使用百分比表示多个步骤 */
@keyframes slideInUp {
  0%   { transform: translateY(20px); opacity: 0; }
  100% { transform: translateY(0);    opacity: 1; }
}

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50%       { transform: scale(1.05); }
}

/* 应用动画 */
.card {
  animation-name: fadeIn;
  animation-duration: 300ms;
  animation-timing-function: ease-out;
  animation-delay: 0ms;
  animation-iteration-count: 1;         /* 或:infinite, 3 */
  animation-direction: normal;          /* 或:reverse, alternate */
  animation-fill-mode: both;            /* 保持最终状态 */
  animation-play-state: running;        /* 或:paused */
  
  /* 简写 */
  animation: fadeIn 300ms ease-out both;
  
  /* 多个动画 */
  animation: fadeIn 300ms ease-out, pulse 2s ease-in-out infinite 300ms;
}

animation-fill-mode(重要!)

/* none:动画结束后元素返回原始状态 */
.element { animation: fadeIn 500ms none; }

/* forwards:保持最终关键帧状态 */
.element { animation: fadeIn 500ms forwards; }

/* backwards:在延迟期间应用第一个关键帧 */
.element { animation: fadeIn 500ms 200ms backwards; }

/* both:同时应用 backwards 和 forwards */
.element { animation: fadeIn 500ms 200ms both; } /* ← 通常是你想要的 */

CSS 动画与过渡:完整指南及示例 插图

实用动画模式

加载骨架屏

@keyframes shimmer {
  0%   { background-position: -200% center; }
  100% { background-position: 200% center; }
}

.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

.skeleton-text  { height: 1em; width: 80%; margin-bottom: 8px; }
.skeleton-title { height: 1.5em; width: 60%; }

旋转加载

@keyframes spin {
  to { transform: rotate(360deg); }
}

.spinner {
  width: 24px;
  height: 24px;
  border: 3px solid rgba(59, 130, 246, 0.2);
  border-top-color: #3b82f6;
  border-radius: 50%;
  animation: spin 600ms linear infinite;
}

通知提示滑入

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

@keyframes slideOutRight {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(110%);
    opacity: 0;
  }
}

.toast {
  animation: slideInRight 250ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

.toast.dismissing {
  animation: slideOutRight 200ms ease-in both;
}

CSS 动画与过渡:完整指南及示例 插图

交错列表动画

@keyframes fadeSlideIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

.list-item {
  animation: fadeSlideIn 300ms ease-out both;
}

/* 使用 nth-child 交错 */
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 50ms; }
.list-item:nth-child(3) { animation-delay: 100ms; }
.list-item:nth-child(4) { animation-delay: 150ms; }

/* 更好:通过 JS 设置 CSS 自定义属性 */
.list-item {
  animation-delay: calc(var(--index, 0) * 50ms);
}
// 在每个元素上设置 --index
document.querySelectorAll('.list-item').forEach((item, i) => {
  item.style.setProperty('--index', i);
});

性能:关键规则

只对 transformopacity 进行动画。 这些在 GPU 合成器线程上运行,不会触发布局。

/* ✅ GPU 加速:流畅 60fps */
.element {
  transform: translateX(100px);  /* 移动 */
  transform: scale(1.1);         /* 缩放 */
  transform: rotate(45deg);      /* 旋转 */
  opacity: 0.5;                  /* 淡入淡出 */
}

/* ❌ 触发布局(昂贵):导致卡顿 */
.element {
  width: 200px;   /* 布局 */
  height: 100px;  /* 布局 */
  top: 20px;      /* 布局 */
  left: 10px;     /* 布局 */
  margin: 10px;   /* 布局 */
}

/* ❌ 触发绘制(中等开销) */
.element {
  background-color: red;  /* 绘制 */
  color: blue;            /* 绘制 */
  box-shadow: ...;        /* 绘制 */
}

will-change

/* 提示浏览器创建新的合成器层 */
.animated-element {
  will-change: transform, opacity;
}

/* ⚠️ 谨慎使用 —— 每个层都会消耗 GPU 内存 */
/* 在动画开始前添加,结束后移除 */
element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});
element.addEventListener('transitionend', () => {
  element.style.willChange = 'auto';
});

无障碍:尊重用户偏好

/* 尊重减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
  /* 为偏好减少动画的用户移除动画 */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* 或提供替代的简化动画 */
@media (prefers-reduced-motion: no-preference) {
  .hero-animation {
    animation: fadeSlideIn 600ms ease-out both;
  }
}

CSS 动画 vs JavaScript 动画

特性 CSS JS(GSAP 等)
性能 ✅ 合成器友好 ✅ 使用得当也友好
控制 有限 完全控制
排序 困难 容易
物理 有(弹簧)
交互性 有限 完全
SVG 路径 有限 支持
最佳用途 简单的状态变化 复杂的时间线

对于大多数 UI 过渡 → CSS。对于复杂的序列/滚动动画 → GSAP 或 Web Animations API。

→ 使用 Color Converter 探索动画的配色方案。