前端动画全景指南
一、前端动画到底是什么
前端动画,就是让画面随时间、用户输入或页面状态连续变化。变化的对象可以是按钮的位置、弹窗的透明度、SVG 的线条、Canvas 里的粒子,也可以是三维场景中的镜头。
先把三个问题分开:
| 问题 | 常见答案 |
|---|---|
| 画什么? | DOM/CSS、SVG、Canvas 2D、GPU 渲染的场景、预制动画素材 |
| 怎样变化? | CSS 过渡/关键帧、Web Animations API、逐帧计算、动画库 |
| 谁来驱动? | 时间、点击、悬停、滚动、拖拽、业务数据、状态机 |
它们可以组合。例如,“滚动时出现一幅会动的插画”,可能由 IntersectionObserver 判断进入视口、CSS 让容器淡入、Lottie 播放插画完成。
因此,CSS、Canvas、GSAP、Lottie 并不是互相替代的同一类工具。选技术之前,先描述清楚效果和交互。
二、前端可以做出哪些动画
| 动画类型 | 具体效果 | 常用方案 |
|---|---|---|
| 操作反馈 | 按钮按压、开关滑动、收藏心跳、错误轻晃 | CSS transition / animation |
| 组件进出场 | 弹窗淡入、抽屉滑入、Toast 出现和退出 | CSS、WAAPI、框架过渡组件 |
| 加载与状态 | 旋转 Loading、骨架扫光、上传进度、成功打勾 | CSS、SVG、Lottie |
| 内容展示 | 标题逐字出现、卡片错峰入场、图片揭幕 | CSS、WAAPI、GSAP、Motion |
| 布局变化 | 列表排序、卡片展开、标签切换、共享元素移动 | FLIP、布局动画库、View Transitions |
| 滚动叙事 | 阅读进度条、视差、滚动控制镜头、分屏故事 | CSS 滚动时间线、GSAP ScrollTrigger |
| 手势交互 | 拖拽、惯性、回弹、卡片甩出 | 指针事件+动画库或物理计算 |
| 矢量图形 | 路径绘制、轨迹运动、图标变形 | SVG+CSS/JS/SMIL |
| 大量二维对象 | 星空、烟花、雨雪、粒子、二维游戏 | Canvas 2D、PixiJS |
| 三维效果 | 模型旋转、产品拆解、空间漫游、Shader 特效 | Three.js 等三维工具 |
| 设计稿动画 | 品牌插画、引导动画、交互角色 | Lottie、Rive |
| 固定片段播放 | 宣传片、循环背景、预渲染复杂效果 | 视频、动画图片、精灵图 |
这个表是选型入口。同一种效果通常有多种做法,例如星空既可以用少量 CSS 元素实现,也可以用 Canvas;对象数量和交互复杂度决定成本。
三、CSS transition:让两个状态自然过渡
**适合:**按钮反馈、颜色变化、卡片抬升、菜单展开等有明确起点和终点的效果。
transform 描述位置、旋转、缩放等变换;transition 描述属性变化的过程。单独写 transform 不会自动产生持续动画。
<button class="action">保存</button>
<style>
.action {
padding: 12px 24px;
border: 0;
border-radius: 10px;
background: #2563eb;
color: white;
transition: transform 160ms ease-out, background-color 160ms;
}
.action:hover,
.action:focus-visible {
background: #1d4ed8;
transform: translateY(-2px);
}
.action:active {
transform: translateY(0) scale(0.97);
}
@media (prefers-reduced-motion: reduce) {
.action { transition: none; }
}
</style>
触发不一定靠 :hover,也可以由 JS 切换类名,或由组件状态改变样式。移动端没有可靠的悬停操作,关键功能要通过点击、触摸和键盘访问。
建议明确列出过渡属性,避免 transition: all 把后来增加的宽高等属性意外纳入动画。MDN:CSS transitions
四、CSS animation:定义多个关键帧
**适合:**循环旋转、呼吸、摇摆、一次性的入场动作,以及有多个阶段的效果。
<span class="spinner" aria-hidden="true"></span>
<span role="status">正在加载…</span>
<style>
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #dbeafe;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 800ms linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
.spinner { animation: none; }
}
</style>
@keyframes 定义阶段;时长、延迟、缓动、重复次数和播放方向由 animation 属性控制。linear 适合匀速旋转,ease-out 常用于逐渐停稳,steps() 可用于精灵图逐帧切换。
animation-fill-mode: forwards 可以保留末尾效果,但不是永久修改业务状态;动画规则移除后,元素仍回到基础样式。关键 UI 的最终状态应由类名或组件状态表达。
transition 与 animation 没有绝对的复杂度界限:前者适合状态间过渡,后者更适合明确编排关键帧和循环。MDN:CSS animations
五、Web Animations API:用原生 JS 控制动画
**适合:**动态生成关键帧,按业务逻辑暂停、继续、倒放、调整进度,或等待动画完成。
<button id="replay">播放入场</button>
<div id="card">这是一张卡片</div>
<script>
const replay = document.querySelector('#replay');
const card = document.querySelector('#card');
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
let activeAnimation;
replay.addEventListener('click', () => {
activeAnimation?.cancel();
if (reducedMotion.matches) return;
activeAnimation = card.animate(
[
{ opacity: 0, transform: 'translateY(16px)' },
{ opacity: 1, transform: 'translateY(0)' }
],
{ duration: 260, easing: 'ease-out' }
);
});
</script>
这里结束后回到卡片的基础样式,基础样式本来就是可见的。无需依靠 fill: 'forwards' 长期占用样式控制权。
animate() 返回 Animation 对象,可以 pause()、play()、reverse()、cancel();也能读取 currentTime 或等待 finished。取消动画会使对应的 finished Promise 拒绝,如果代码等待它,要处理取消分支。
WAAPI 不等于手动逐帧写样式:浏览器管理关键帧和时间线。是否能在合成线程高效运行,还取决于属性及渲染条件。MDN:Web Animations API
六、requestAnimationFrame:每一帧自己算
**适合:**物理模拟、鼠标跟随、实时绘图、游戏循环,以及无法方便地用关键帧表达的逻辑。
下面是一个 JS 函数,调用 move(document.querySelector('#box')),让已有元素在约 600ms 内向右移动 240px;返回值用于中途取消。
function move(element) {
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
element.style.transform = 'translateX(240px)';
return () => {};
}
let start;
let frameId;
function step(now) {
start ??= now;
const progress = Math.min((now - start) / 600, 1);
const eased = 1 - (1 - progress) ** 3;
element.style.transform = `translateX(${240 * eased}px)`;
if (progress < 1) frameId = requestAnimationFrame(step);
}
frameId = requestAnimationFrame(step);
return () => cancelAnimationFrame(frameId);
}
关键点是根据经过时间计算进度。每帧固定 x += 5,会让高刷新率屏幕上的移动速度更快。rAF 是一次性请求,要继续动画就再次申请;它不保证固定 60fps,后台页面通常还会暂停回调。MDN:requestAnimationFrame
这段是单次移动示例;重复启动前应取消旧循环。真实物理模拟还要定义切后台后的时间策略,避免恢复时把很大的时间差直接用于积分。
setInterval(fn, 16) 并不等于稳定 60fps,也不与浏览器绘制同步。普通 DOM 过渡优先选择 CSS 或 WAAPI,确实需要逐帧计算时再写循环。
七、滚动动画:先区分“触发”和“控制进度”
1. 滚到某处,开始播放
例如卡片进入视口后淡入一次。这属于滚动触发:触发后,动画按时间播放,用户停止滚动也会继续。
可用 IntersectionObserver 判断元素进入视口,再添加 class 或调用 animate()。它适合可见性阈值判断,不是逐像素滚动进度接口。MDN:Intersection Observer
内容默认应可见。不要先把整页设为 opacity: 0,却没有处理 JS 加载失败、浏览器不支持、减少动态效果等分支,否则正文可能一直隐藏。
2. 滚多少,动画就走多少
例如阅读进度条、图片随滚动缩放、产品拆解。这属于滚动驱动:滚动位置决定动画进度,往回滚时通常也会往回走。
CSS 可以通过 animation-timeline: scroll() 关联滚动进度,通过 view() 关联元素在滚动容器中的可见进度。以下是阅读进度条:
<div class="reading-progress" aria-hidden="true"></div>
<style>
.reading-progress { display: none; }
@supports (animation-timeline: scroll()) {
@media (prefers-reduced-motion: no-preference) {
.reading-progress {
display: block;
position: fixed;
inset: 0 0 auto 0;
height: 3px;
background: #2563eb;
transform-origin: left;
animation: reading linear both;
animation-timeline: scroll(root block);
}
}
}
@keyframes reading {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
</style>
页面需要足够长才能滚动。这里将 animation-timeline 放在 animation 简写后面,避免被简写重置;不支持时不显示装饰进度条,正文仍正常使用。MDN:CSS scroll-driven animations
多段滚动编排、固定区段、复杂时间线可以评估 GSAP ScrollTrigger。长页面内容和字体加载可能改变几何尺寸,不能把初次计算的坐标当作永远有效。GSAP 文档
八、布局动画与页面切换
1. FLIP:列表重排也能平滑移动
FLIP 是一种思路,不是某个 CSS 属性:
- First:记录变化前的位置和大小。
- Last:更新布局,记录变化后的几何信息。
- Invert:用 transform 把元素在视觉上放回旧位置。
- Play:把 transform 动画到正常位置,看起来就像平滑移动过去。
适合列表排序、网格切换、卡片展开。它减少对中间布局状态逐帧插值的依赖,但仍涉及前后测量;复杂嵌套、滚动容器和缩放失真需要处理。可以使用成熟布局动画能力,如 GSAP Flip。GSAP Flip
2. View Transitions:连接更新前后的画面
适合路由切换、列表到详情的共享元素、主题切换等。浏览器为变化前后画面建立过渡,开发者再控制过渡样式。
下面是概念代码,renderNextView() 需要替换成项目自己的 DOM 或路由更新逻辑:
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!document.startViewTransition || reduced) {
renderNextView();
} else {
document.startViewTransition(() => renderNextView());
}
命名共享元素时,可用 view-transition-name 建立对应关系。同一时刻名称冲突会造成问题。SPA 的同文档 API 与跨文档导航的启用方式、限制并不相同;过渡本身也不会替你实现路由、焦点管理和浏览器历史。MDN:View Transition API
3. 折叠面板:高度变化是常见难点
传统方案可以测量内容高度,在 0px 与明确高度之间过渡,结束后恢复自适应;还要考虑内容更新和快速反向操作。max-height 方案写起来简单,但预设上限可能截断内容,实际视觉时长也可能不符合设置值。
新的 interpolate-size: allow-keywords 能在支持条件下帮助长度与固有尺寸关键字之间插值;不能据此假设所有浏览器、所有 auto 变化都能平滑动画。上线前检查兼容性并提供可直接展开的回退。MDN:interpolate-size
九、SVG、Canvas 和三维分别擅长什么
1. SVG:可缩放的图形和路径
适合图标、线条、进度环、路径绘制、沿曲线运动。SVG 元素可以被 CSS 或 JS 操作,也能使用 SMIL 描述部分动画。
常见“画线”效果是改变 stroke-dashoffset,并与 stroke-dasharray 配合。路径形变则需要考虑路径数据的可插值性和结构,不能假定任意两条复杂路径都能自然变形。
优点是图形清晰、能单独操作节点;大量 SVG 节点仍有 DOM 和绘制成本。MDN:SVG animation
2. Canvas 2D:在画布上重绘一帧
适合粒子、星空、烟花、雨雪、音频可视化、二维游戏。典型过程是更新对象数据、清理或覆盖旧画面,再绘制新画面。
画布里的粒子不是独立 DOM 节点。命中检测、交互、可访问性等通常需要另外实现。高分屏要协调 CSS 尺寸与实际画布像素尺寸;盲目按很高 DPR 渲染,会显著增加像素处理成本。MDN:Canvas 动画
当大量二维精灵、纹理和场景管理成为主要需求,可以评估 PixiJS 等渲染工具,而不必自己从底层管理所有绘制。PixiJS 官方指南
3. 三维与 Shader:需要真正的空间或像素特效
模型旋转、灯光材质、空间镜头、产品拆解,可选择 Three.js 之类的三维工具。液体、扭曲、溶解等效果也可通过 Shader 实现。
CSS 的 perspective 和三维 transform 能做翻牌、立方体、倾斜卡片等效果,但没有因此获得完整三维场景的模型、灯光和材质系统。
三维方案还要承担模型、纹理、资源释放、加载反馈及设备 GPU 差异的成本。一个普通按钮无需引入整套三维工具。Three.js 官方文档
十、动画库和设计工具怎么选
| 工具或类别 | 擅长什么 | 使用前考虑 |
|---|---|---|
| GSAP | 时间线、多元素编排、错峰、滚动和 SVG 配合 | 是否确实需要编排能力;按需使用插件 |
| Motion | React 等环境中的声明式动画、手势、弹簧和布局变化 | 核对对应框架 API、退出和布局机制 |
| Anime.js | JS 动画、时间线、SVG 与属性变化 | 按当前文档接入,不混用旧版本教程 |
| Animate.css 一类预设 CSS 库 | 快速套用现成入场、强调效果 | 预设是否符合产品,是否加载过多样式 |
| Lottie | 播放设计工具导出的动画,常用于插画、Loading | 导出特性是否受运行时支持,素材及渲染成本 |
| Rive | 带状态机和输入的交互动画、角色反馈 | 设计文件和状态机要与业务事件配合 |
库的能力依据:GSAP、Motion、Anime.js、Lottie Web、Rive Web。
Lottie 并不是“把任意视频转成很小的 JSON”;特性支持、外部图片和渲染器都会影响结果。Rive 更适合需要输入和状态转换的设计动画,仍要用代码把点击、业务状态等输入接进去。
如果只需要播放一个固定片段,视频可能更直接;动画图片也能播放,但精细控制播放、时间轴和减少动态效果时通常不如可控运行时方便。选素材方案也要比较文件体积、解码、清晰度及透明背景需求。
十一、React、Vue 项目怎样接动画
框架主要管理状态、组件和 DOM 生命周期,底层仍是前面这些动画能力。
**React:**简单效果用状态切 class;复杂进出场、布局和手势可以用 Motion。退出动画要在 DOM 移除之前播放,不能先把条件渲染设为 false,再期待元素继续淡出。对应的存在状态管理工具可以延迟卸载。Motion 动画文档
**Vue:**单元素或组件进出场可用 <Transition>;列表移动可用 <TransitionGroup>,需要稳定 key,并按机制配置样式。它们可以对接 CSS,也可以接 JS 钩子。Vue Transition
两类框架都要处理:
- 组件卸载时,取消 rAF、动画实例、监听器和观察器。
- 快速连续点击时,明确是取消、反向、接续还是排队。
- 同一个 transform 避免被 CSS、框架样式和动画库同时争抢;必要时用外层负责布局、内层负责动效。
- 不把每帧粒子坐标都塞进组件状态引发整棵界面更新;高频绘制使用合适的专用状态与渲染循环。
- SSR 页面保持服务端和初次客户端输出可预测,不让动画初始化造成闪烁或隐藏正文。
十二、让动画舒服:不只是技术能实现
1. 先定义目的和节奏
按钮反馈应及时;弹窗要让用户理解它从哪里出现;删除和重排动画帮助用户跟踪元素去向。持续装饰动画则要控制存在感。
时长可以从下面的经验范围试起,这不是浏览器标准,也不是所有产品都适用的固定值:
| 效果 | 初始调参范围 |
|---|---|
| 按钮按压、轻微反馈 | 约 100~180ms |
| 下拉菜单、小卡片变化 | 约 180~280ms |
| 弹窗、抽屉、较大的布局变化 | 约 220~400ms |
| 展示型叙事动画 | 按内容编排,允许跳过或中断 |
位移越大,通常越需要足够时间理解;但不要让动画延迟实际操作。弹簧效果关注刚度、阻尼和初速度,不必强行用一个固定时长描述所有回弹。
2. 为减少动态效果提供替代
使用 prefers-reduced-motion 读取用户偏好。可减少大幅位移、旋转和视差,直接展示最终状态;保留“正在加载”等文字或静态状态信息。
CSS 媒体查询不会自动关闭 JS 动画、Canvas、视频和第三方运行时,这些方案需要各自接入偏好。长期运行的页面还应处理偏好变化。MDN:prefers-reduced-motion
不要让“动画结束事件是否触发”决定表单能否提交、内容能否显示等核心逻辑。透明度为 0 的控件也可能仍可点击或聚焦,隐藏状态要同时考虑交互与语义。
十三、动画卡顿时,怎么查
1. 看整条渲染链路
浏览器可能需要经历样式计算、布局、绘制、合成。布局和绘制越频繁、覆盖面积越大,每帧成本越高。
移动、缩放、淡入淡出时,优先评估 transform 和 opacity;宽高、位置和复杂绘制属性可能带来更高成本。但这不意味着任何 transform 都一定被 GPU 加速,或 CSS 一定比 JS 快。属性、图层、内容、设备共同决定结果。web.dev:高性能动画、MDN:CSS 与 JS 动画性能
2. 常见问题与处理
| 问题 | 处理方向 |
|---|---|
| 每帧改 width、height、top、left | 检查是否可改用 transform;确需布局变化则缩小影响范围 |
| 写样式后立刻读尺寸,循环往复 | 批量读取,再批量写入,减少同步布局 |
| 全屏模糊、大阴影、大量半透明叠加 | 减少面积和层数,录制性能确认瓶颈 |
| 粒子太多或 DPR 太高 | 限制数量、绘制尺寸和更新成本,按设备降级 |
| 到处写 will-change | 只在确有收益时短期使用;过多图层消耗内存 |
| 页面离开后动画仍运行 | 清理循环、监听器和实例,离屏或不可见时暂停 |
| 每次交互不断新增动画 | 明确取消/复用策略,避免重复循环 |
| 只在桌面看起来顺畅 | 在目标手机及 WebView 上测滚动、触摸、发热与恢复 |
这些都是排查方向,最终以 Performance 录制和目标设备表现为准。渲染与图层优化
3. 不只看“平均帧率”
60Hz 屏幕一帧约 16.7ms,120Hz 约 8.3ms,而且这些时间还要留给浏览器其他工作。关注长帧、掉帧、输入响应和持续运行成本,而不是只看平均 FPS。rAF 与刷新率
建议验证:低性能设备、快速反复点击、滚动中触发、调整窗口、切后台再返回、减少动态效果、组件反复挂载卸载,以及新 API 不支持时的回退。
新特性的支持情况会变。Scroll-driven Animations、View Transitions、固有尺寸插值等,都应查看文档兼容表并验证目标浏览器;“某版本浏览器支持”不等于用户手机里的 WebView 一定支持。
十四、拿到需求后,照这个顺序选
- 普通界面状态过渡? 先看 CSS transition。
- 循环或多关键帧效果? 先看 CSS animation。
- 需要 JS 动态控制播放、暂停、倒放? 看 WAAPI;多元素编排再评估动画库。
- 元素进入视口后播放? IntersectionObserver 配合动画。
- 滚动直接控制进度? CSS 滚动时间线或 ScrollTrigger。
- 排序、布局、页面间共享元素? FLIP、布局动画库或 View Transitions。
- 矢量线条和图标? SVG;大量粒子和精灵?Canvas / PixiJS。
- 真正三维场景? Three.js 等工具。
- 设计师提供现成动效? 评估 Lottie、Rive;固定片段也可考虑视频。
- 需要实时物理和自定义绘制? rAF+时间计算,配合相应渲染方式。
这是选型顺序,不是技术高低排序。满足效果、交互、性能和维护要求的最简单方案,通常就是合适的方案。
十五、建议的学习与练习路线
| 阶段 | 学习内容 | 练习目标 |
|---|---|---|
| 1 | transform、opacity、transition、缓动 | 按钮反馈、菜单过渡 |
| 2 | @keyframes、时序、进出场 | Loading、弹窗、错峰卡片 |
| 3 | WAAPI、rAF、时间与取消 | 播放控制、跟随和中断 |
| 4 | IntersectionObserver、滚动时间线、FLIP | 内容入场、阅读进度、列表重排 |
| 5 | 性能录制、减少动态效果、框架生命周期 | 做成稳定可复用的业务组件 |
| 6 | 根据方向选 SVG / Canvas / 动画库 / 三维 | 选择一个真实需求深入 |
如果您觉得这篇文章有帮助,请点个赞吧~
评论
请登录后发表评论
去登录