CSS 动画与过渡:让页面动起来

2024-08-29 前端

前言

在现代 Web 开发中,动画早已不是锦上添花的点缀,而是提升用户体验的核心手段之一。无论是按钮反馈、页面转场、加载指示器,还是品牌展示的微交互,流畅自然的动画都能让用户感到界面是"活"的、有反馈的。CSS 提供了两大核心动画方案——过渡(Transition) 和动画(Animation),配合 Transform 变形能力,开发者无需引入 JavaScript 库即可实现绝大多数 UI 动效。

本文将从基础语法出发,覆盖所有核心知识点,并给出大量可直接运行的实战案例,帮助读者系统掌握 CSS 动画技术栈。


一、CSS 过渡(Transition)

CSS 过渡允许我们在 CSS 属性值发生变化时,不是瞬间跳变,而是在一段时间内平滑变化。它是最简单、最常用的 CSS 动画方式。

1.1 过渡的四要素

一个过渡由四个子属性构成:

/* 分别定义 */
.box {
  transition-property: background-color;
  transition-duration: 0.3s;
  transition-timing-function: ease;
  transition-delay: 0s;
}

/* 简写形式(推荐) */
.box {
  transition: background-color 0.3s ease 0s;
}

各属性含义如下:

属性 含义 默认值
transition-property 指定要过渡的属性名 all
transition-duration 过渡持续时长 0s(无过渡)
transition-timing-function 缓动函数,控制变化速率 ease
transition-delay 延迟多久后开始过渡 0s

1.2 过渡多个属性

可以同时过渡多个属性,用逗号分隔:

.card {
  width: 200px;
  height: 200px;
  background: #3498db;
  transition: width 0.3s ease,
              height 0.3s ease,
              background-color 0.5s ease-in;
}

.card:hover {
  width: 300px;
  height: 300px;
  background-color: #e74c3c;
}

如果多个属性使用相同的时长和缓动函数,也可以简写为:

.card {
  transition: all 0.3s ease; /* 过渡所有可过渡属性 */
}

⚠️ 注意:all 虽然方便,但会监听所有属性变化,可能带来不必要的性能开销。建议显式列出需要过渡的属性。

1.3 缓动函数详解

缓动函数决定了动画的加速度曲线,是让动画看起来"自然"的关键。

预定义关键字

/* 五种内置缓动函数 */
.slow-start {
  transition-timing-function: ease;           /* 慢→快→慢,默认 */
}
.linear-motion {
  transition-timing-function: linear;         /* 匀速 */
}
.fast-start {
  transition-timing-function: ease-in;        /* 慢→快 */
}
.fast-end {
  transition-timing-function: ease-out;       /* 快→慢 */
}
.ease-both {
  transition-timing-function: ease-in-out;    /* 慢→快→慢,比 ease 更平缓 */
}

cubic-bezier() 自定义曲线

cubic-bezier(x1, y1, x2, y2) 接受四个参数,定义一条三次贝塞尔曲线:

.bounce {
  /* 回弹效果:超出目标再拉回 */
  transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55);
}

.elastic {
  /* 弹性伸缩效果 */
  transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

常见缓动曲线的贝塞尔值:

效果 cubic-bezier 值
ease 0.25, 0.1, 0.25, 1.0
linear 0.0, 0.0, 1.0, 1.0
ease-in 0.42, 0.0, 1.0, 1.0
ease-out 0.0, 0.0, 0.58, 1.0
ease-in-out 0.42, 0.0, 0.58, 1.0

💡 在线工具 cubic-bezier.com 可以直观地调试和预览贝塞尔曲线。

steps() 逐帧函数

steps(n, direction) 将过渡分成 n 个离散步骤,而不是平滑过渡:

/* 分成 4 帧,每帧结束时变化 */
.step-anim {
  transition-timing-function: steps(4, end);
}

/* 分成 4 帧,每帧开始时变化 */
.step-anim-start {
  transition-timing-function: steps(4, start);
}

steps() 常配合精灵图(sprite sheet)实现逐帧动画,我们将在实战部分详细展开。

1.4 过渡触发条件

过渡不是自动发生的,它需要属性的值发生变化。常见的触发方式有三种:

伪类触发

.btn {
  background: #3498db;
  transform: scale(1);
  transition: all 0.2s ease;
}

.btn:hover {
  background: #2980b9;
  transform: scale(1.05);
}

.btn:active {
  transform: scale(0.95);
}

.btn:focus {
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.4);
}

class 切换

<button onclick="toggleMenu()">菜单</button>
<div id="menu" class="menu">菜单内容</div>

<style>
.menu {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.menu.open {
  max-height: 500px; /* 足够大的值 */
}
</style>

<script>
function toggleMenu() {
  document.getElementById('menu').classList.toggle('open');
}
</script>

JavaScript 直接修改样式

const el = document.querySelector('.box');
el.style.opacity = '0';
// 触发过渡(需要先读取一次布局值,避免合并)
requestAnimationFrame(() => {
  el.style.opacity = '1';
});

1.5 可过渡属性列表

并非所有 CSS 属性都可以过渡。可过渡属性通常满足以下条件:

  1. 数值型:如 width、height、margin、padding、font-size
  2. 颜色型:如 color、background-color、border-color
  3. 变换型:如 transform、opacity
  4. 部分其他:如 visibility(离散过渡)、z-index(仅在整数之间)

常见不可过渡属性:

  • display(只能用 JS 配合 setTimeout/requestAnimationFrame 模拟)
  • background-image(除非使用渐变)
  • font-family
  • position
  • float

1.6 height: auto 不能过渡的原因与解决方案

这是一个经典问题:当我们想让元素的高度从 0 变到 auto 时,直接写 transition: height 0.3s 是无效的。

原因:auto 不是一个具体的数值,浏览器无法在过渡开始时计算出中间帧。

方案一:max-height 技巧

.accordion {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.accordion.open {
  max-height: 500px; /* 设置为一个比实际内容更大的值 */
}

缺点:如果 max-height 设置过大,动画会有延迟感。建议设为略高于实际高度的值或使用 JS 动态计算。

方案二:scaleY + transform-origin

.accordion {
  transform: scaleY(0);
  transform-origin: top center;
  transition: transform 0.3s ease;
}

.accordion.open {
  transform: scaleY(1);
}

方案三:grid-template-rows(现代方案)

.accordion {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease;
}

.accordion > .inner {
  overflow: hidden;
}

.accordion.open {
  grid-template-rows: 1fr;
}

这是 2024 年后最推荐的方案,无需预设高度,且动画流畅。


二、CSS 动画(Animation)

CSS 过渡只能处理"从 A 到 B"的单次变化,而 CSS 动画通过 @keyframes 可以定义多阶段、循环或复杂的动画序列。

2.1 @keyframes 定义

@keyframes 规则定义动画的关键帧:

/* 使用百分比定义多阶段 */
@keyframes slideIn {
  0% {
    transform: translateX(-100%);
    opacity: 0;
  }
  50% {
    transform: translateX(10%);
    opacity: 0.7;
  }
  100% {
    transform: translateX(0);
    opacity: 1;
  }
}

/* 只有开始和结束,可用 from/to 简写 */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

2.2 动画的八大属性

.element {
  /* 逐个定义 */
  animation-name: slideIn;          /* 动画名称 */
  animation-duration: 1s;           /* 持续时长 */
  animation-timing-function: ease;  /* 缓动函数 */
  animation-delay: 0s;              /* 延迟时间 */
  animation-iteration-count: 1;     /* 播放次数(infinite 为无限) */
  animation-direction: normal;      /* 方向 */
  animation-fill-mode: none;        /* 填充模式 */
  animation-play-state: running;    /* 播放状态 */

  /* 简写形式 */
  animation: slideIn 1s ease 0s 1 normal none running;
}
属性 取值 说明
animation-name @keyframes 名称 引用定义的动画
animation-duration 时间值(如 0.5s) 必须设置,默认 0s 不播放
animation-timing-function ease/linear/cubic-bezier() 等 同 transition
animation-delay 时间值 可为负值,负值让动画从中间开始
animation-iteration-count 数字 或 infinite 0.5 表示只播放前半段
animation-direction normal / reverse / alternate / alternate-reverse 动画播放方向
animation-fill-mode none / forwards / backwards / both 动画前后样式保持
animation-play-state running / paused 控制暂停/播放

2.3 animation-direction 详解

@keyframes move {
  from { transform: translateX(0); }
  to   { transform: translateX(200px); }
}

/* 正向:0 → 200px */
.dir-normal {
  animation-direction: normal;
}

/* 反向:200px → 0 */
.dir-reverse {
  animation-direction: reverse;
}

/* 交替:0→200px→0→200px... */
.dir-alternate {
  animation-direction: alternate;
  animation-iteration-count: infinite;
}

/* 反向交替:200px→0→200px→0... */
.dir-alternate-reverse {
  animation-direction: alternate-reverse;
  animation-iteration-count: infinite;
}

2.4 animation-fill-mode 详解

fill-mode 控制动画在播放前和播放后如何应用样式:

/* 不保留任何样式(默认) */
.fill-none {
  animation: fadeIn 1s;
  /* 播放前:元素原始样式
     播放后:回到原始样式 */
}

/* 保留最后一帧 */
.fill-forwards {
  animation: fadeIn 1s;
  animation-fill-mode: forwards;
  /* 播放后:保持在 100% 帧的样式 */
}

/* 立即应用到第一帧 */
.fill-backwards {
  animation: fadeIn 1s;
  animation-delay: 1s;
  animation-fill-mode: backwards;
  /* 播放前(包括 delay 期间):保持 0% 帧的样式 */
}

/* 同时应用 forwards + backwards */
.fill-both {
  animation: fadeIn 1s;
  animation-delay: 1s;
  animation-fill-mode: both;
}

2.5 多动画叠加

一个元素可以同时应用多个动画,用逗号分隔:

.spinner {
  animation:
    spin 1s linear infinite,
    pulse 2s ease-in-out infinite,
    colorShift 3s linear infinite;
}

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

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

@keyframes colorShift {
  0%   { border-color: #e74c3c; }
  33%  { border-color: #f1c40f; }
  66%  { border-color: #2ecc71; }
  100% { border-color: #e74c3c; }
}

2.6 动画事件

CSS 动画会触发三个 JavaScript 事件,方便我们在动画的不同阶段执行逻辑:

const el = document.querySelector('.animated');

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

el.addEventListener('animationend', (e) => {
  console.log('动画结束', e.animationName);
  // 动画结束后移除元素
  el.remove();
});

el.addEventListener('animationiteration', (e) => {
  console.log('新一轮迭代开始', e.animationName);
});

完整示例:

<div class="toast">消息已发送</div>

<script>
const toast = document.querySelector('.toast');
toast.addEventListener('animationend', () => {
  toast.style.display = 'none';
});
</script>

<style>
.toast {
  animation: slideUp 0.3s ease forwards;
}

@keyframes slideUp {
  from {
    transform: translateY(100%);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
</style>

2.7 动画性能注意事项

  1. 优先使用 transform 和 opacity:它们不触发重排(reflow)和重绘(repaint),仅触发合成(composite)。
  2. 避免动画 width、height、top、left:每次变化都会触发布局计算。
  3. 适当使用 will-change:为即将动画的元素设置 will-change 可提示浏览器提前优化。
  4. 不要过度使用 infinite 动画:持续运行的动画会增加 GPU 和 CPU 负担。

三、CSS Transform

transform 属性允许我们对元素进行平移、旋转、缩放、倾斜等变换,是 CSS 动画中最核心的搭档。

3.1 2D 变换函数

translate() — 平移

.translate-demo {
  /* 单轴 */
  transform: translateX(50px);   /* X 轴右移 50px */
  transform: translateY(-30px);  /* Y 轴上移 30px */

  /* 双轴 */
  transform: translate(50px, -30px);

  /* 百分比:相对于自身宽高 */
  transform: translate(50%, -50%); /* 水平居中技巧 */
}

/* 经典水平垂直居中 */
.center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

rotate() — 旋转

.rotate-demo {
  transform: rotate(45deg);    /* 顺时针 45° */
  transform: rotate(-90deg);   /* 逆时针 90° */
  transform: rotate(1turn);    /* 一整圈 */
  transform: rotate(0.5turn);  /* 半圈 */
}

scale() — 缩放

.scale-demo {
  transform: scale(1.5);          /* 等比例放大 1.5 倍 */
  transform: scale(0.5);          /* 缩小一半 */
  transform: scaleX(2);           /* X 轴拉伸 2 倍 */
  transform: scaleY(0.8);         /* Y 轴压缩到 80% */
  transform: scale(1.2, 0.8);     /* X 和 Y 不同比例 */
}

skew() — 倾斜

.skew-demo {
  transform: skew(10deg);          /* X 轴倾斜 10° */
  transform: skew(10deg, 5deg);    /* X 轴 10°, Y 轴 5° */
  transform: skewX(-15deg);        /* X 轴反向倾斜 */
  transform: skewY(20deg);         /* Y 轴倾斜 */
}

matrix() — 矩阵变换

matrix(a, b, c, d, tx, ty) 是 2D 变换的通用矩阵形式,包含缩放、旋转、倾斜和平移。通常不用手写,但了解其原理有助于深入理解变换本质:

/* matrix(scaleX, skewY, skewX, scaleY, translateX, translateY) */
/* translate(50px, 30px) 等价于 */
transform: matrix(1, 0, 0, 1, 50, 30);

/* rotate(45deg) 等价于 */
transform: matrix(0.7071, 0.7071, -0.7071, 0.7071, 0, 0);

/* scale(2, 1.5) 等价于 */
transform: matrix(2, 0, 0, 1.5, 0, 0);

3.2 transform-origin

transform-origin 设置变换的原点(默认是元素中心 50% 50%):

.origin-demo {
  transform: rotate(45deg);

  /* 关键字 */
  transform-origin: top left;       /* 左上角 */
  transform-origin: center;         /* 中心(默认) */
  transform-origin: right bottom;   /* 右下角 */

  /* 数值 */
  transform-origin: 0 0;            /* 左上角 */
  transform-origin: 100px 200px;    /* 指定坐标 */
  transform-origin: 50% 100%;       /* 底部中心 */
}

示例:绕左下角旋转的钟摆效果

.pendulum {
  transform-origin: top center;
  animation: swing 1s ease-in-out infinite alternate;
}

@keyframes swing {
  from { transform: rotate(-30deg); }
  to   { transform: rotate(30deg); }
}

3.3 3D Transform

CSS 3D 变换让元素在三维空间中变换,为页面带来真正的立体感。

基础 3D 函数

.three-d-demo {
  /* 3D 平移 */
  transform: translateZ(100px);       /* 向用户方向移动 */
  transform: translate3d(x, y, z);    /* 3D 平移简写 */

  /* 3D 旋转 */
  transform: rotateX(45deg);          /* 绕 X 轴旋转(上下翻转) */
  transform: rotateY(45deg);          /* 绕 Y 轴旋转(左右翻转) */
  transform: rotateZ(45deg);          /* 绕 Z 轴旋转(平面旋转,同 rotate()) */
  transform: rotate3d(x, y, z, deg);  /* 绕任意轴旋转 */

  /* 3D 缩放 */
  transform: scaleZ(1.5);             /* Z 轴缩放(影响厚度) */
  transform: scale3d(x, y, z);        /* 3D 缩放简写 */
}

perspective — 透视

透视是 3D 效果的关键,它决定了观察者到物体的距离:

/* 方式一:在父容器上设置透视 */
.scene {
  perspective: 800px;         /* 观察距离 800px */
  perspective-origin: 50% 50%; /* 观察点位置 */
}

.scene .card {
  transform: rotateY(45deg);
}

/* 方式二:在 transform 中直接设置 */
.card {
  transform: perspective(800px) rotateY(45deg);
}

两种方式的区别:

  • 父容器设置 perspective:所有子元素共享同一个透视空间
  • transform: perspective():每个元素独立计算透视

transform-style: preserve-3d

默认情况下,子元素的 3D 变换会被"拍平"到父元素所在的平面。preserve-3d 保留子元素的 3D 位置:

.scene {
  perspective: 800px;
  transform-style: preserve-3d; /* 保留子元素 3D 位置 */
}

.card-front,
.card-back {
  backface-visibility: hidden;  /* 隐藏背面 */
}

.card-back {
  transform: rotateY(180deg);   /* 背面旋转 180° */
}

backface-visibility

控制元素背面是否可见,在卡片翻转效果中至关重要:

.card-face {
  backface-visibility: hidden;   /* 背面不可见 */
  /* 或 */
  backface-visibility: visible;  /* 背面可见(默认) */
}

3.4 3D 卡片翻转效果

这是一个完整的 3D 翻转卡片案例:

3.5 性能提示:transform + opacity 走合成层

浏览器渲染流程可分为以下步骤:

JavaScript → Style → Layout → Paint → Composite
  • Layout(重排):计算元素的位置和大小。改变 width、height、top、left 等会触发。
  • Paint(重绘):绘制像素。改变 color、background-color、box-shadow 等会触发。
  • Composite(合成):将图层合成到屏幕上。改变 transform 和 opacity 仅触发合成。

核心原则:尽可能只使用 transform 和 opacity 做动画,避免触发 Layout 和 Paint。

/* ❌ 不好:触发重排 */
.bad {
  animation: badMove 1s ease infinite;
}
@keyframes badMove {
  from { left: 0; }
  to   { left: 100px; }
}

/* ✅ 好:仅触复合层 */
.good {
  animation: goodMove 1s ease infinite;
}
@keyframes goodMove {
  from { transform: translateX(0); }
  to   { transform: translateX(100px); }
}

四、will-change 与性能优化

4.1 哪些属性触发重排/重绘/合成

触发类别 属性举例 性能影响
仅合成 transform、opacity ✅ 最优
重绘+合成 color、background-color、box-shadow、border-radius ⚠️ 中等
重排+重绘+合成 width、height、top、left、margin、padding、font-size、border-width ❌ 最差

4.2 will-change 的使用时机

will-change 属性告诉浏览器某个元素即将发生变化,让浏览器提前做好优化准备:

/* 告诉浏览器 opacity 将发生变化 */
.element {
  will-change: opacity;
}

/* 多个属性 */
.element {
  will-change: transform, opacity;
}

/* 告诉浏览器将创建新的层叠上下文 */
.element {
  will-change: transform;
}

使用原则:

  1. 不要对大量元素使用:每个 will-change 都会创建新的合成层,过多会耗尽 GPU 内存。
  2. 在变化前添加,变化后移除:这是理想做法,通常用 JS 控制:
const el = document.querySelector('.animated');

// 动画开始前添加
el.addEventListener('mouseenter', () => {
  el.style.willChange = 'transform';
});

// 动画结束后移除
el.addEventListener('transitionend', () => {
  el.style.willChange = 'auto';
});
  1. 不要对无动画的元素使用:will-change 本身有性能开销。

4.3 强制 GPU 合成

在少数情况下,浏览器可能没有将元素提升到合成层,我们可以通过一些技巧强制触发:

/* 方法一:使用 will-change */
.gpu-layer {
  will-change: transform;
}

/* 方法二:使用 3D transform 强制提升 */
.gpu-layer {
  transform: translateZ(0);  /* 或 translate3d(0,0,0) */
}

/* 方法三:使用 backface-visibility */
.gpu-layer {
  backface-visibility: hidden;
}

注意:过度使用 GPU 合成会占用显存,尤其在有大量动画元素时可能适得其反。只在必要的时候使用。


五、实战效果

5.1 加载动画

旋转加载器

<div class="spinner"></div>

<style>
.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #e0e0e0;
  border-top-color: #3498db;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}
</style>

进度条动画

<div class="progress-bar">
  <div class="progress-fill"></div>
</div>

<style>
.progress-bar {
  width: 300px;
  height: 8px;
  background: #e0e0e0;
  border-radius: 4px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #3498db, #2ecc71);
  border-radius: 4px;
  animation: fillProgress 2s ease-out forwards;
}

@keyframes fillProgress {
  from { width: 0%; }
  to   { width: 80%; }
}
</style>

骨架屏

5.2 悬浮效果

缩放 + 阴影

边框动画

5.3 页面进入动画(Intersection Observer + 样式)

利用 Intersection Observer API 监听元素进入视口,再通过 class 切换触发动画:

5.4 3D 翻转卡片(完整版)

5.5 无缝轮播

实际生产环境中的无缝轮播通常用 JS + transform 配合 transition 实现更精细的控制,这里展示的是纯 CSS 方案的工作原理。

5.6 呼吸灯效果

5.7 逐帧动画(steps + sprite)

逐帧动画使用 steps() 配合精灵图(sprite sheet)实现类似 GIF 的动画效果:

<div class="sprite-animation"></div>

<style>
.sprite-animation {
  width: 100px;          /* 一帧的宽度 */
  height: 100px;         /* 一帧的高度 */
  background: url('https://via.placeholder.com/600x100/3498db/ffffff?text=012345') no-repeat;
  /* 假设精灵图:6 帧水平排列,总宽 600px */
  background-size: 600px 100px;
  animation: spritePlay 0.6s steps(5, end) infinite;
}

@keyframes spritePlay {
  from {
    background-position: 0 0;      /* 第一帧 */
  }
  to {
    background-position: -600px 0; /* 最后一帧(超出,被 steps 截断) */
  }
}
</style>

关键原理:

  • 精灵图宽 600px,每帧 100px,共 6 帧
  • steps(5, end) 将动画分为 5 步(从第 0 帧到第 5 帧,共 6 帧)
  • background-position 从 0 移动到 -600px,每次跳跃 100px

实际工作中的精灵图示例(以奔跑的小人为例):

.runner {
  width: 64px;
  height: 64px;
  background: url('run-sprite.png') no-repeat;
  /* 假设 8 帧横向排列,总宽 512px */
  background-size: 512px 64px;
  animation: run 0.8s steps(7, end) infinite;
}

@keyframes run {
  from { background-position: 0 0; }
  to   { background-position: -512px 0; }
}

六、CSS vs JS 动画

6.1 对比概览

维度 CSS 动画 JS 动画(requestAnimationFrame)
易用性 ⭐⭐⭐⭐⭐ 声明式,几行代码 ⭐⭐⭐ 需要手动控制
性能 ⭐⭐⭐⭐⭐ 浏览器优化,走合成层 ⭐⭐⭐⭐ 需注意优化
控制力 ⭐⭐⭐ 有限(暂停/反转/时间线) ⭐⭐⭐⭐⭐ 完全控制
复杂序列 ⭐⭐ 需要多 keyframes 或延迟 ⭐⭐⭐⭐⭐ 任意逻辑
交互绑定 ⭐⭐ 需借助 class/css 变量 ⭐⭐⭐⭐⭐ 可响应拖拽、滚动等
兼容性 ⭐⭐⭐⭐ 现代浏览器支持好 ⭐⭐⭐⭐⭐ 全平台一致

6.2 何时用 CSS

  • 简单的一次性过渡:按钮悬浮、表单聚焦、菜单展开/收起
  • 持续的 UI 微交互:加载旋转、脉冲效果、呼吸灯
  • 进入/退出动画:配合 visibility 或 class 切换
  • 页面加载动画:从初始状态到最终状态

6.3 何时用 JS

  • 复杂的时间线编排:多个动画按精确时间轴执行
  • 动态计算:动画路径、时长、参数由用户交互或数据决定
  • 物理模拟:弹簧效果、贝塞尔模拟、粒子系统
  • 滚动驱动动画:视差滚动、滚动进度条
  • 暂停/倒放/跳转:视频播放器式的动画控制

6.4 requestAnimationFrame 示例

function animate(element, targetX, duration = 1000) {
  const startX = parseFloat(getComputedStyle(element).left) || 0;
  const startTime = performance.now();

  function frame(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1); // 0 → 1

    // easeOut 缓动
    const eased = 1 - Math.pow(1 - progress, 3);
    const currentX = startX + (targetX - startX) * eased;

    element.style.transform = `translateX(${currentX}px)`;

    if (progress < 1) {
      requestAnimationFrame(frame);
    }
  }

  requestAnimationFrame(frame);
}

// 使用
const box = document.querySelector('.box');
animate(box, 300, 1500);

6.5 Web Animations API 简介

Web Animations API 是浏览器原生提供的 JS 动画接口,结合了 CSS 动画的声明式简洁和 JS 的过程式控制:

Web Animations API vs CSS 动画:

/* CSS 方式 */
.css-box {
  animation: slide 2s ease infinite alternate;
}

@keyframes slide {
  from { transform: translateX(0); }
  to   { transform: translateX(300px); }
}
// WAAPI 方式(完全等价)
const box = document.querySelector('.waapi-box');
box.animate([
  { transform: 'translateX(0)' },
  { transform: 'translateX(300px)' }
], {
  duration: 2000,
  easing: 'ease',
  iterations: Infinity,
  direction: 'alternate'
});

Web Animations API 的优势:

  1. 可以动态创建和修改动画参数
  2. 提供完整的播放控制(暂停、倒放、跳转)
  3. 支持动画组合和时序编排
  4. 性能等同 CSS 动画(底层相同)

七、总结与最佳实践

核心原则回顾

  1. 优先用 transform 和 opacity 做动画:它们仅触发合成层,性能最优。
  2. CSS 过渡用于简单状态变化:hover、focus、class 切换等单次变化。
  3. CSS 动画用于复杂多阶段效果:循环、多关键帧、需要方向控制。
  4. JS 动画用于需要动态控制的高阶场景:物理模拟、滚动驱动、时间线编排。
  5. 合理使用 will-change:为即将动画的元素提前通知浏览器,但用完即弃。
  6. 用 Web Animations API 替代手写 rAF:在需要 JS 控制时优先选择原生 API。

性能检查清单

  • [ ] 动画属性是否只用 transform 和 opacity?
  • [ ] 是否避免了 width、height、top、left 的动画?
  • [ ] 是否对长时间运行的动画设置了 will-change?
  • [ ] 动画元素数量是否可控(建议不超过几十个)?
  • [ ] 移动端是否测试了动画流畅度?
  • [ ] animation-iteration-count: infinite 是否必要?

常用动画速查表

场景 推荐方案 关键属性
按钮悬浮 CSS Transition transform: scale() + box-shadow
弹窗出现 CSS Animation + JS 控制 @keyframes + animation-fill-mode: both
列表入场 Intersection Observer + CSS Transition 动态添加 class
加载旋转 CSS Animation rotate() + infinite
轮播滚动 JS + CSS Transition / WAAPI 动态 transform: translateX()
卡片翻转 CSS 3D Transition rotateY() + preserve-3d + backface-visibility
逐帧动画 CSS Animation + steps() background-position + steps()
物理弹簧 JS + rAF 或 WAAPI 自定义缓动函数

参考资源