首页/文章/前端

前端动画全景指南

2026-10-15
7210 字约 25 分钟
...

一、前端动画到底是什么

前端动画,就是让画面随时间、用户输入或页面状态连续变化。变化的对象可以是按钮的位置、弹窗的透明度、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 属性:

  1. First:记录变化前的位置和大小。
  2. Last:更新布局,记录变化后的几何信息。
  3. Invert:用 transform 把元素在视觉上放回旧位置。
  4. 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 配合是否确实需要编排能力;按需使用插件
MotionReact 等环境中的声明式动画、手势、弹簧和布局变化核对对应框架 API、退出和布局机制
Anime.jsJS 动画、时间线、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 一定支持。

十四、拿到需求后,照这个顺序选

  1. 普通界面状态过渡? 先看 CSS transition。
  2. 循环或多关键帧效果? 先看 CSS animation。
  3. 需要 JS 动态控制播放、暂停、倒放? 看 WAAPI;多元素编排再评估动画库。
  4. 元素进入视口后播放? IntersectionObserver 配合动画。
  5. 滚动直接控制进度? CSS 滚动时间线或 ScrollTrigger。
  6. 排序、布局、页面间共享元素? FLIP、布局动画库或 View Transitions。
  7. 矢量线条和图标? SVG;大量粒子和精灵?Canvas / PixiJS。
  8. 真正三维场景? Three.js 等工具。
  9. 设计师提供现成动效? 评估 Lottie、Rive;固定片段也可考虑视频。
  10. 需要实时物理和自定义绘制? rAF+时间计算,配合相应渲染方式。

这是选型顺序,不是技术高低排序。满足效果、交互、性能和维护要求的最简单方案,通常就是合适的方案。

十五、建议的学习与练习路线

阶段学习内容练习目标
1transform、opacity、transition、缓动按钮反馈、菜单过渡
2@keyframes、时序、进出场Loading、弹窗、错峰卡片
3WAAPI、rAF、时间与取消播放控制、跟随和中断
4IntersectionObserver、滚动时间线、FLIP内容入场、阅读进度、列表重排
5性能录制、减少动态效果、框架生命周期做成稳定可复用的业务组件
6根据方向选 SVG / Canvas / 动画库 / 三维选择一个真实需求深入

 

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
前端2026-07-17
HTML Form 可以把 POST 响应加载进 iframe
在接入支付验证、身份认证或第三方授权页面时,我们经常会遇到这样的流程: 1. 后端返回一个第三方接口地址。 2. 这个地址必须通过 请求访问。 3. 接口返回 HTML,或者重定向到真正的验证页面。 4. 验证页面需要嵌入当前网站,而不是打开新标签页。 5. 验证完成后,当前页面继续执行后续业务。 第一次遇到这个需求,很容易想到: 但涉及第三方支付时,这种方式通常会遇到 CORS、Cookie、重定向和跨域页面读取限制。 实际上,HTM...
学习
前端2026-05-29
前端判断一个网页是否允许被 iframe 内嵌
前端 不能 100% 准确提前判断 一个网页是否允许被 iframe 内嵌,因为决定权主要在目标网站返回的 HTTP 响应头 里,而普通前端 JS 通常读不到跨域页面的响应头。 核心判断看这两个东西: 1. 老一点但仍常见。 表示完全不允许被 iframe 嵌入。 表示只允许同源页面 iframe 嵌入。 以前有这个,但现代浏览器支持很差,基本不建议依赖。 2. 现在更推荐看这个。 表示不允许任何页面嵌入。 表示只允许同源嵌入。 表示只...
学习
前端2026-03-20
网页唤起 Android App 调试笔记(App Links)
&nbsp; 在最近的项目中,我遇到了 网页访问特定 URL 应该唤起 Android App,但实际只打开网页 的问题。通过调试和排查,整理出以下经验和步骤。 1\. 项目与环境信息(脱敏处理) 网站域名: 唤起页面: Android App 包名: intent filter 配置示例: assetlinks.json 已上线,包含 App 的 SHA256 签名指纹 2\. 问题现象 用户访问 页面时, 网页打开了,但 App 没...
学习
前端2026-03-02
深入理解 Cookie:安全属性、访问边界与渲染场景实践
本文系统梳理 Cookie 核心机制,聚焦 HttpOnly/SameSite 等安全属性 、 访问权限边界 、 CSR/SSR 差异 ,附关键代码示例与安全清单。适合开发查阅与知识沉淀。 一、Cookie 是什么?为什么需要它? HTTP 是无状态协议。Cookie 是服务端通过 响应头下发、浏览器自动存储并在后续 同源请求中携带 的小型文本数据(通常 ≤4KB),用于: 会话维持(Session ID) 用户偏好(语言/主题) 跨请...
学习
前端2026-03-02
退出登录时 Cookie 清除指南
核心结论 :登出 ≠ 单方面操作。HttpOnly 与非 HttpOnly Cookie 需 前后端协同清除 ,缺一不可。残留 Cookie = 安全隐患 + 用户体验漏洞。 🔒 为什么不能“一键清空”? 浏览器出于安全设计: 后端 :只能通过 响应头清除 自己设置过 的 Cookie(需属性完全匹配) 前端 JS :可读写非 HttpOnly Cookie,但 无法触碰 HttpOnly Cookie 无“清除全部”API :任何服...
学习
前端2025-12-15
懒加载图片在同一个 Item 中逐个出现的原因与解决方案
懒加载图片在同一个 Item 中逐个出现的原因与解决方案 适用场景 :使用原生 实现图片懒加载的动态列表,每个列表项(item)包含多张图片。 📌 问题描述 在开发一个动态渲染的列表时,每个 item 中包含三张图片,并使用 HTML 原生的懒加载属性: 观察到的现象: 开启懒加载时 :三张图片 不是同时出现 ,而是 从左到右(或从上到下)依次加载显示 ,有明显的时间差。 关闭懒加载(移除 )后 :三张图片 看起来是一起出现的 ,视觉...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录