技术分享CSS动画

CSS动画基础:用transition和keyframes创造流畅交互

好的动画是无形的——用户不会说「这个动画很好」,他们只会说「这个应用用起来很舒服」。掌握 CSS 动画,是连通界面与直觉的必修课。

2026年02月05日
16分钟

2008 年我刚入行时,Flash 还活着(尽管已经在喘息)。现在回想起来,Flash 教会了整整一代设计师一件 CSS 后来也学会了的事:动画不是装饰。动画是信息在时间维度上的编排。

这篇文章的目标读者是已经能写出静态页面、但想系统掌握 Web 动画的前端开发者。我不会罗列所有 CSS 动画属性——MDN 已经做得很好了。我要写的是实战中的心智模型、性能陷阱和可维护模式。

Section 01动画的本质:不是“动”,而是“变化被看见”

页面中的变化本来一直都在发生:按钮状态变了、卡片出现了、列表被排序了、内容被插入了。动画的作用不是制造变化,而是让变化变得可理解。

如果一切变化都瞬间完成,用户会失去空间定位和因果理解;如果变化太慢,用户会觉得系统在拖延。动画的价值就在这两者之间。

动画服务于认知

一个好的界面动画,通常在做四件事:

  • 指示层级变化
  • 维持空间连续性
  • 引导注意力
  • 提供反馈

也就是说,动画不是为了“好看”,而是为了减少用户理解页面变化时的心理成本。

Section 02Transition:状态间的桥梁

transition 是动画世界最简单、也最高频使用的工具。它处理的是状态间的自动补间:一个元素从 A 状态到 B 状态需要经过多少时间、走什么曲线。

四要素

css
1.button {
2 background: var(--color-accent);
3 transform: scale(1);
4 transition: background 0.2s ease-out,
5 transform 0.15s ease-out;
6}
7
8.button:hover {
9 background: var(--color-accent-hover);
10 transform: scale(1.02);
11}
  1. property:要过渡的属性。尽量指定具体属性,不用 all——性能更好,意外更少
  2. duration:时长。交互反馈推荐 100-300ms,超过 500ms 用户会开始等
  3. timing-function:缓动函数。这是动画的灵魂
  4. delay:延迟。极少在交互动画中使用,但在编排(stagger)中是关键

缓动函数的心理效应

缓动物理感觉最佳场景
ease-out物体在减速元素出现、展开
ease-in物体在加速元素消失、关闭
ease-in-out对称的加速/减速循环动画、缩放
linear匀速——机械加载指示器、旋转
自定义 cubic-bezier品牌个性界面签名动画
css
1/* 品牌弹跳——只适合特殊强调 */
2--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
3
4/* 高质量通用缓出 */
5--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
6
7/* Apple 风格的减速感 */
8--ease-apple: cubic-bezier(0.25, 0.1, 0.25, 1);

Section 03@keyframes:编排时间轴

当动画超越了「从 A 到 B」而需要多阶段表达时,@keyframes 登场:

css
1@keyframes fadeInUp {
2 0% {
3 opacity: 0;
4 transform: translateY(16px);
5 }
6 100% {
7 opacity: 1;
8 transform: translateY(0);
9 }
10}
11
12.card {
13 animation: fadeInUp 0.5s ease-out both;
14}

animation 属性速查

css
1.card {
2 animation-name: fadeInUp;
3 animation-duration: 0.5s;
4 animation-timing-function: ease-out;
5 animation-delay: 0.1s;
6 animation-iteration-count: 1;
7 animation-direction: normal;
8 animation-fill-mode: both;
9}

animation-fill-mode: both 是高频使用的设置:元素在动画开始前保持第一帧样式,动画结束后保持最后一帧。它在避免「闪烁」方面至关重要。

Section 04编排(Stagger):动画的音乐性

一个页面中的多个元素不应该同时入场。它们应该像音符一样有先后秩序:

css
1.card:nth-child(1) { animation-delay: 0ms; }
2.card:nth-child(2) { animation-delay: 80ms; }
3.card:nth-child(3) { animation-delay: 160ms; }
4.card:nth-child(4) { animation-delay: 240ms; }

Tailwind 中可以直接写入 inline style 或使用自定义 delay utilities。本博客文章列表的入场动画就使用了这个模式,每张卡延迟 80ms。

编排的黄金法则:总延迟不应超过 300ms——从第一个元素到最后一个元素的入场间隔如果太长,用户会产生「这个页面还在加载」的焦虑。

Section 05性能:只动画 transform 和 opacity

浏览器渲染管道由四个阶段组成:样式 → 布局 → 绘制 → 合成。

text
1Layout → 改变 width / height / left / top
2Paint → 改变 color / background / box-shadow
3Composite → 改变 transform / opacity

每一帧(16.7ms @ 60fps)中,Layout 和 Paint 是昂贵的;Composite 是廉价的。因此:

css
1/* ❌ 触发布局和绘制 */
2@keyframes bad-move {
3 from { left: 0; top: 0; background: red; }
4 to { left: 100px; top: 50px; background: blue; }
5}
6
7/* ✅ 仅触发合成 */
8@keyframes good-move {
9 from { transform: translate(0, 0); opacity: 0.5; }
10 to { transform: translate(100px, 50px); opacity: 1; }
11}

will-change 的正确用法

will-change 提前告诉浏览器「这个元素将发生变化」,让浏览器预先创建合成层:

css
1.slide-panel {
2 will-change: transform;
3 transition: transform 0.3s ease-out;
4}

注意:will-change 应该仅用于即将发生动画的元素,并且在动画结束后移除。滥用它会让浏览器为从未动画的元素预分配 GPU 内存。

prefers-reduced-motion

任何动画都应尊重用户的系统级偏好:

css
1@media (prefers-reduced-motion: reduce) {
2 *,
3 *::before,
4 *::after {
5 animation-duration: 0.01ms !important;
6 transition-duration: 0.01ms !important;
7 }
8}

这不是「禁用」动画——而是将动画时间压缩到几乎不可见,保留操作性的动画(如焦点环、页面切换),同时去除纯装饰性的动画。

Section 06动画设计中最常见的几个误区

1. 把动画做成装饰

如果动画没有提供层级、反馈或方向感,它通常只是噪音。

2. 把所有动效做得一样

大按钮和小图标、页面切换和 hover 反馈,它们的目标不同,时间感也不应该一样。

3. 忽略可访问性

对某些用户来说,强烈的位移、闪烁或持续循环会带来不适。prefers-reduced-motion 不是可选项。

4. 只盯着单个元素

真正的动画设计要关注的是整个界面的节奏,不只是某个元素单独好不好看。

Section 07实战场景

按钮点击反馈

css
1.button:active {
2 transform: scale(0.96);
3 transition: transform 0.1s ease;
4}

页面加载编排

css
1.hero-title { animation: fadeInUp 0.6s ease-out both; }
2.hero-text { animation: fadeInUp 0.6s ease-out 0.1s both; }
3.hero-cta { animation: fadeInUp 0.6s ease-out 0.2s both; }

卡片 hover 提升

css
1.card {
2 transform: translateY(0);
3 box-shadow: 0 2px 8px rgba(0,0,0,0.1);
4 transition: transform 0.25s ease-out,
5 box-shadow 0.25s ease-out;
6}
7
8.card:hover {
9 transform: translateY(-4px);
10 box-shadow: 0 12px 30px rgba(0,0,0,0.15);
11}

页面切换的视觉连续性

如果一个页面从列表切换到详情页,最好的体验不是“跳转成功”,而是“我刚刚点的那张卡变成了详情页”。这种连续性会极大降低认知成本。

Section 08结语

动画是时间轴上的排版。和字体排印一样,它是一门可以被学习、被演练、被精通的工艺。它不是一个「会动就行」的 afterthought——它是你的界面在时间维度上的完整表达。