Skip to content

深入理解 CSS 动画完整体系

transition、animation、Web Animations API、滚动驱动动画、View Transitions 五套系统对比

什么是 CSS 动画?

定义:CSS 动画是浏览器提供的声明式运动系统,通过描述元素属性值随时间变化的方式来产生视觉效果。CSS 目前有五套动画机制:transition(过渡)、@keyframes + animation(关键帧动画)、Web Animations API(JS 控制)、Scroll-Driven Animations(滚动驱动)和 View Transitions API(页面过渡)。

涉及场景

  • 交互反馈:按钮 hover 变色、菜单展开收起、模态框淡入淡出
  • 加载指示:骨架屏闪烁、旋转加载器、进度条动画
  • 滚动体验:滚动进度条、视差效果、元素进入视口时的渐入
  • 页面过渡:SPA 路由切换动画、MPA 跨页面过渡(View Transitions)
  • 数据可视化:图表数据更新时的平滑过渡
  • 游戏 / 创意:Canvas 动画循环、粒子效果、3D 变换

作用

  1. 提升用户体验:流畅的动画让界面更有"质感",减少操作的突兀感
  2. 引导用户注意力:通过运动吸引用户关注重要信息或操作入口
  3. 性能关键:只动画 transform + opacity 可在合成线程运行,不阻塞主线程
  4. 面试常考:transition 与 animation 区别、性能优化(will-change / 合成层)是高频考点

一、Transition(过渡)

最简单的动画方式——属性值从 A 变到 B 时自动产生过渡效果。

css
.box {
  width: 100px;
  background: blue;

  /* 完整语法 */
  transition-property: width, background;   /* 过渡哪些属性 */
  transition-duration: 0.3s, 0.5s;          /* 过渡时长 */
  transition-timing-function: ease-in-out;  /* 缓动函数 */
  transition-delay: 0s, 0.1s;              /* 延迟 */
  transition-behavior: allow-discrete;      /* 允许离散属性过渡(新) */

  /* 简写 */
  transition: width 0.3s ease-in-out, background 0.5s ease 0.1s;
  /* 或 */
  transition: all 0.3s ease;
}

.box:hover {
  width: 200px;
  background: red;
}

缓动函数完整列表

css
/* 预定义关键字 */
transition-timing-function: ease;        /* 默认,慢-快-慢 */
transition-timing-function: linear;      /* 匀速 */
transition-timing-function: ease-in;     /* 慢-快(加速) */
transition-timing-function: ease-out;    /* 快-慢(减速) */
transition-timing-function: ease-in-out; /* 慢-快-慢 */

/* 贝塞尔曲线 */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); /* 弹性效果 */

/* 阶梯函数 */
transition-timing-function: steps(4, jump-start);
transition-timing-function: steps(4, jump-end);   /* 等价于 step-end */
transition-timing-function: steps(4, jump-both);
transition-timing-function: steps(4, jump-none);
transition-timing-function: step-start;            /* = steps(1, jump-start) */
transition-timing-function: step-end;              /* = steps(1, jump-end) */

/* 线性缓动函数(CSS 新特性) */
transition-timing-function: linear(0, 0.25 75%, 1);
/* 可以定义任意曲线!更灵活 */

/* 弹簧效果(通过 linear() 实现) */
transition-timing-function: linear(
  0, 0.006, 0.025, 0.058, 0.104, 0.163, 0.234, 0.315, 0.406,
  0.503, 0.604, 0.705, 0.804, 0.896, 0.979, 1.05, 1.107,
  1.15, 1.176, 1.189, 1.189, 1.178, 1.158, 1.132, 1.101,
  1.069, 1.038, 1.01, 0.986, 0.966, 0.952, 0.943, 0.939,
  0.939, 0.943, 0.95, 0.96, 0.971, 0.983, 0.994, 1.004,
  1.012, 1.017, 1.02, 1.02, 1.018, 1.013, 1.007, 1.001, 1
);

可过渡属性

css
/* 大多数数值型属性可以过渡 */
/* ✅ 可过渡 */
width, height, margin, padding, border-width
top, left, right, bottom
opacity
color, background-color, border-color
transform
box-shadow, text-shadow
font-size, letter-spacing, line-height

/* ❌ 不可过渡(离散值) */
display          /* 但 transition-behavior: allow-discrete 后可以! */
visibility       /* 可以过渡(visible ↔ hidden) */
position
float
overflow

/* 新特性:离散属性过渡(2024+) */
.modal {
  display: none;
  opacity: 0;
  transition: display 0.3s allow-discrete, opacity 0.3s;
}
.modal.open {
  display: block;
  opacity: 1;
}
/* @starting-style 定义初始状态 */
@starting-style {
  .modal.open {
    opacity: 0;
  }
}

Transition 事件

javascript
element.addEventListener('transitionstart', (e) => {
  console.log(`${e.propertyName} 开始过渡`);
});

element.addEventListener('transitionend', (e) => {
  console.log(`${e.propertyName} 过渡完成,耗时 ${e.elapsedTime}s`);
});

element.addEventListener('transitioncancel', (e) => {
  console.log(`${e.propertyName} 过渡被取消`);
});

element.addEventListener('transitionrun', (e) => {
  console.log(`${e.propertyName} 过渡启动(包括delay期间)`);
});

二、Animation(关键帧动画)

比 transition 更强大——可以定义多个关键帧、循环播放、暂停控制。

css
/* 定义关键帧 */
@keyframes bounce {
  0% {
    transform: translateY(0);
    animation-timing-function: ease-out;
  }
  50% {
    transform: translateY(-30px);
    animation-timing-function: ease-in;
  }
  100% {
    transform: translateY(0);
  }
}

/* 应用动画 */
.element {
  /* 完整属性 */
  animation-name: bounce;              /* 关键帧名称 */
  animation-duration: 1s;              /* 时长 */
  animation-timing-function: ease;     /* 缓动(可在关键帧中覆盖) */
  animation-delay: 0.5s;              /* 延迟 */
  animation-iteration-count: infinite; /* 循环次数(infinite = 无限) */
  animation-direction: alternate;      /* 方向 */
  animation-fill-mode: forwards;       /* 结束后保持状态 */
  animation-play-state: running;       /* running | paused */
  animation-composition: replace;      /* replace | add | accumulate */
  animation-timeline: auto;            /* auto | scroll() | view() */

  /* 简写 */
  animation: bounce 1s ease 0.5s infinite alternate forwards;
}

animation-direction

css
animation-direction: normal;            /* 正向播放 */
animation-direction: reverse;           /* 反向播放 */
animation-direction: alternate;         /* 交替:正-反-正-反... */
animation-direction: alternate-reverse; /* 交替:反-正-反-正... */

animation-fill-mode

css
animation-fill-mode: none;      /* 默认:动画前后不应用关键帧样式 */
animation-fill-mode: forwards;  /* 动画结束后保持最后一帧 */
animation-fill-mode: backwards; /* delay期间应用第一帧 */
animation-fill-mode: both;      /* forwards + backwards */

animation-composition(新特性)

css
/* 多个动画叠加时如何组合 */
@keyframes slide { to { transform: translateX(100px); } }
@keyframes scale { to { transform: scale(1.5); } }

.element {
  transform: rotate(45deg); /* 基础值 */
  animation: slide 1s, scale 1s;
  animation-composition: add; /* 叠加而非替换 */
  /* 最终:rotate(45deg) translateX(100px) scale(1.5) */
}

Animation 事件

javascript
element.addEventListener('animationstart', (e) => {
  console.log(`动画 ${e.animationName} 开始`);
});

element.addEventListener('animationiteration', (e) => {
  console.log(`动画 ${e.animationName} 完成一次迭代`);
});

element.addEventListener('animationend', (e) => {
  console.log(`动画 ${e.animationName} 结束`);
});

element.addEventListener('animationcancel', (e) => {
  console.log(`动画 ${e.animationName} 被取消`);
});

三、Web Animations API(WAAPI)

用 JavaScript 控制动画,与 CSS 动画底层共享同一引擎。

javascript
// 基本用法
const animation = element.animate(
  // 关键帧(等价于 @keyframes)
  [
    { transform: 'translateX(0)', opacity: 1 },
    { transform: 'translateX(300px)', opacity: 0 }
  ],
  // 选项
  {
    duration: 1000,        // 毫秒
    easing: 'ease-in-out',
    delay: 0,
    iterations: Infinity,  // 循环次数
    direction: 'alternate',
    fill: 'forwards',
    composite: 'replace'   // replace | add | accumulate
  }
);

// 控制方法
animation.play();      // 播放
animation.pause();     // 暂停
animation.reverse();   // 反向
animation.cancel();    // 取消
animation.finish();    // 跳到结尾
animation.persist();   // 持久化(防止被GC)

// 属性
animation.currentTime;     // 当前时间(ms)
animation.playbackRate;    // 播放速率(2 = 2倍速,-1 = 反向)
animation.playState;       // 'idle' | 'running' | 'paused' | 'finished'
animation.startTime;       // 开始时间
animation.effect;          // 动画效果对象

// Promise
animation.finished.then(() => {
  console.log('动画完成');
});
animation.ready.then(() => {
  console.log('动画准备好了');
});

// 获取元素上的所有动画
const animations = element.getAnimations();
// 获取包括子元素的动画
const allAnimations = element.getAnimations({ subtree: true });
// 获取文档上的所有动画
const docAnimations = document.getAnimations();

// 序列动画
async function sequentialAnimations(element) {
  await element.animate(
    [{ opacity: 0 }, { opacity: 1 }],
    { duration: 500, fill: 'forwards' }
  ).finished;

  await element.animate(
    [{ transform: 'scale(1)' }, { transform: 'scale(1.2)' }],
    { duration: 300, fill: 'forwards' }
  ).finished;

  await element.animate(
    [{ transform: 'scale(1.2)' }, { transform: 'scale(1)' }],
    { duration: 300, fill: 'forwards' }
  ).finished;
}

四、滚动驱动动画(Scroll-Driven Animations)

2024年新特性,用滚动进度驱动动画,无需 JavaScript。

css
/* 基于滚动进度的动画 */
@keyframes progressBar {
  from { width: 0%; }
  to { width: 100%; }
}

.progress {
  position: fixed;
  top: 0;
  height: 3px;
  background: blue;
  animation: progressBar linear;
  animation-timeline: scroll();  /* 绑定到最近的滚动容器 */
}

/* scroll() 函数参数 */
animation-timeline: scroll();                    /* 默认:最近的块级滚动祖先 */
animation-timeline: scroll(root);                /* 根滚动容器(viewport) */
animation-timeline: scroll(nearest);             /* 最近的滚动祖先 */
animation-timeline: scroll(self);                /* 自身滚动 */
animation-timeline: scroll(root block);          /* 指定轴:block / inline / x / y */

/* 基于元素可见性的动画 */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(50px); }
  to { opacity: 1; transform: translateY(0); }
}

.card {
  animation: fadeIn linear both;
  animation-timeline: view();   /* 元素进入/离开视口时触发 */
  animation-range: entry 0% entry 100%; /* 进入视口时播放 */
}

/* animation-range 值 */
animation-range: entry;         /* 元素进入视口 */
animation-range: exit;          /* 元素离开视口 */
animation-range: contain;       /* 元素完全在视口中 */
animation-range: cover;         /* 从开始进入到完全离开 */
animation-range: entry 25% exit 75%; /* 自定义范围 */

/* 命名时间线(多个元素共享) */
.scroller {
  scroll-timeline-name: --my-timeline;
  scroll-timeline-axis: block;
}

.animated-element {
  animation-timeline: --my-timeline;
}

/* 视图时间线命名 */
.target {
  view-timeline-name: --card-timeline;
  view-timeline-axis: block;
}

五、View Transitions API

页面状态变化时的平滑过渡动画。

css
/* 基本的 View Transition 样式 */
::view-transition-old(root) {
  animation: fadeOut 0.3s ease;
}
::view-transition-new(root) {
  animation: fadeIn 0.3s ease;
}

/* 为特定元素命名 */
.hero-image {
  view-transition-name: hero;
}

/* 自定义特定元素的过渡 */
::view-transition-old(hero) {
  animation: scaleDown 0.4s ease;
}
::view-transition-new(hero) {
  animation: scaleUp 0.4s ease;
}

/* 跨文档 View Transition(MPA) */
@view-transition {
  navigation: auto;
}
javascript
// SPA 中的 View Transition
async function navigate(url) {
  const transition = document.startViewTransition(async () => {
    // 更新 DOM
    const response = await fetch(url);
    const html = await response.text();
    document.querySelector('main').innerHTML = html;
  });

  // 等待过渡完成
  await transition.finished;
}

// 自定义过渡类型
document.startViewTransition({
  update: () => updateDOM(),
  types: ['slide-left']  // 可以根据类型应用不同 CSS
});

动画性能优化

css
/* 1. 只动画合成属性(transform + opacity) */
/* ✅ 不触发重排(reflow)和重绘(repaint) */
transform: translateX() / scale() / rotate()
opacity: 0-1

/* ❌ 触发重排(代价最高) */
width, height, margin, padding, top, left

/* ❌ 触发重绘 */
color, background, box-shadow, border

/* 2. will-change 提示浏览器 */
.animated {
  will-change: transform, opacity;
  /* 浏览器会提前创建合成层 */
  /* ⚠️ 不要过度使用,每个合成层消耗内存 */
}

/* 3. 使用 transform: translateZ(0) 或 translate3d 强制GPU加速 */
.gpu-accelerated {
  transform: translateZ(0);
}

/* 4. 减少动画元素数量 */
/* 5. 使用 requestAnimationFrame 替代 setInterval */
/* 6. 使用 CSS 动画替代 JS 动画(浏览器可以优化) */

/* 7. contain 属性限制重绘范围 */
.card {
  contain: layout paint;
}

/* 8. content-visibility 延迟渲染 */
.offscreen {
  content-visibility: auto;
  contain-intrinsic-size: 200px;
}

五套动画系统对比

特性transitionanimationWAAPI滚动驱动View Transition
触发方式状态变化自动/类名JS调用滚动DOM更新
关键帧2个(起止)多个多个复用@keyframes自动生成
循环跟随滚动
JS控制有限有限✅ 完全有限
暂停/恢复N/A
性能极好
适用场景hover/状态切换装饰性动画复杂交互滚动效果页面过渡

总结

CSS 动画核心知识点:
┌──────────────────────────────────────────────────────────┐
│ Transition                                                │
│ • 最简单的 A→B 过渡,适合 hover/状态切换                     │
│ • transition-behavior: allow-discrete 可过渡 display       │
│ • @starting-style 定义进入动画的初始状态                     │
├──────────────────────────────────────────────────────────┤
│ Animation                                                 │
│ • @keyframes 多关键帧,支持循环、方向、暂停                   │
│ • animation-composition 控制多动画叠加方式                   │
├──────────────────────────────────────────────────────────┤
│ Web Animations API                                        │
│ • element.animate() JS控制动画                             │
│ • play/pause/reverse/cancel/finish 完整控制                │
│ • 序列动画用 async/await + .finished                       │
├──────────────────────────────────────────────────────────┤
│ 滚动驱动动画                                               │
│ • animation-timeline: scroll() / view()                   │
│ • 无需 JS,纯 CSS 实现滚动进度条、滚动视差                   │
├──────────────────────────────────────────────────────────┤
│ 性能                                                      │
│ • 只动画 transform + opacity(合成属性)                     │
│ • 合理使用 will-change 和 contain                          │
│ • CSS 动画优于 JS 动画(浏览器可优化到合成线程)              │
└──────────────────────────────────────────────────────────┘