首页
/ Angular 动画入门指南:用 @angular/animations 构建状态过渡动画

Angular 动画入门指南:用 @angular/animations 构建状态过渡动画

2026-09-06 13:04:44作者:董灵辛Dennis

本文以 Angular 官方动画入门指南为主体,完整讲解如何使用 @angular/animations 包定义动画状态、配置过渡时长与缓动、将触发器绑定到模板,并从一个可运行的 open-close 按钮动画示例出发,逐层剖析 trigger()state()style()animate()transition() 等核心函数的协作机制。读完你能掌握 Angular 动画的完整接入流程、时序参数语法,并能对照仓库源码理解各函数的真实定义与导出路径。

重要提示@angular/animations 包现已弃用(deprecated)。Angular 团队建议使用原生 CSS 配合 animate.enteranimate.leave 来为新代码编写动画。若你在维护存量项目,可参考 迁移指南 了解如何迁移到纯 CSS 动画;新动画范式详见 进入与离开动画指南CSS 动画指南。本文聚焦于理解仍在大量存量应用中被使用的传统动画 API,是迁移前的必要知识基础。

动画的价值:不只是视觉装饰

动画提供了"运动的错觉":HTML 元素随时间改变其样式。一个设计良好的动画能让应用更直观、更有吸引力,但它并非仅仅是装饰——它能从多个维度改善用户体验:

  • 没有动画时,页面切换会显得生硬、突兀;
  • 运动能极大增强体验,动画让用户感知到应用正在响应他们的操作;
  • 优秀的动画能把用户的注意力直觉地引向需要关注的地方。

通常,动画涉及对多个样式的变换(transformations),随时间展开。一个 HTML 元素可以移动、变色、放大或缩小、淡入淡出,或从页面滑出。这些变化可以同时进行,也可以依次进行,并且你可以控制每个变换的时机。

Angular 的动画系统构建在 CSS 能力之上,这意味着你可以动画化浏览器认为可动画化的任何属性,包括位置、尺寸、变换、颜色、边框等。W3C 在其 CSS Transitions 规范中维护了可动画属性的完整清单。

开始之前:启用动画模块

动画涉及的两个主要 Angular 模块是 @angular/animations@angular/platform-browser。要为项目启用动画,你需要导入动画相关模块,并将其与标准 Angular 功能一起提供到应用的提供者列表中。

方式一:bootstrapApplication(推荐)

对于基于 bootstrapApplication 的独立组件应用,从 @angular/platform-browser/animations/async 导入 provideAnimationsAsync,并加入 bootstrapApplication 的 providers 列表:

bootstrapApplication(AppComponent, {
  providers: [provideAnimationsAsync()],
});

提示(若需要在应用加载时立即触发动画):如果希望动画在应用加载的瞬间就发生,应切换到急切加载(eagerly loaded)的动画模块。此时请从 @angular/platform-browser/animations 导入 provideAnimations,并在 bootstrapApplication 调用中provideAnimations 替换 provideAnimationsAsync。两者的关键区别在于加载时机:Async 版本在需要时才加载动画引擎以减小初始包体积,急切版本则保证首帧即可播放动画。

方式二:NgModule 应用

对于基于 NgModule 的传统应用,导入 BrowserAnimationsModule,将动画能力引入 Angular 根应用模块。仓库中的示例文件 app.module.1.ts 展示了标准写法:

import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@NgModule({
  imports: [BrowserModule, BrowserAnimationsModule],
  declarations: [],
  bootstrap: [],
})
export class AppModule {}

在组件文件中导入动画函数

如果你计划在组件文件中使用具体的动画函数,请从 @angular/animations 导入它们。示例文件 app.ts 展示了这一导入方式:

import {
  trigger,
  state,
  style,
  animate,
  transition,
} from '@angular/animations';

这些函数最终都定义并导出自动画包的核心入口。从源码结构看,packages/animations/src/animations.ts@angular/animations 模块的公共 API 入口,它统一导出了 animateanimateChildanimationgroupkeyframesquerysequencestaggerstatestyletransitiontriggeruseAnimation 等全部动画函数及相关元数据类型(如 AnimationStateMetadataAnimationTriggerMetadataAnimationAnimateMetadata)。这意味着上一步中 from '@angular/animations' 导入的每一个符号,都对应包内一处真实的实现导出。

添加动画元数据属性

在组件文件中,于 @Component() 装饰器内添加一个名为 animations: 的元数据属性,将定义动画的触发器(trigger)放在该属性中。示例如下(取自 app.ts):

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrls: ['app.css'],
  animations: [
    // 动画触发器放在这里
  ],
})

动画一次状态过渡:open-close 示例

下面通过一个具体例子,把单个 HTML 元素从一种状态过渡到另一种状态。假设一个按钮根据用户最后动作显示 OpenClosed:处于 open 状态时,它可见且为黄色;处于 closed 状态时,它半透明且为蓝色。

先在终端运行以下命令生成组件:

ng g component open-close

这会在 src/app/open-close.ts 创建该组件。接下来分三步定义状态、过渡与触发器。

动画状态与样式

使用 state() 函数为每次过渡结束时调用定义不同的状态。它接收两个参数:一个唯一名称(如 openclosed)和一个 style() 函数。style() 用于定义与某个状态名关联的一组样式。对于包含连字符的样式属性,必须使用 camelCase(如 backgroundColor),或用引号包裹(如 'background-color')。

open-close.ts 中,open 状态同时设置了多个样式属性:按钮高度为 200 像素、不透明度为 1、背景为黄色:

state(
  'open',
  style({
    height: '200px',
    opacity: 1,
    backgroundColor: 'yellow',
  }),
),

对应的 closed 状态:高度 100 像素、不透明度 0.8、背景为蓝色:

state(
  'closed',
  style({
    height: '100px',
    opacity: 0.8,
    backgroundColor: 'blue',
  }),
),

过渡与时序

在 Angular 中,你可以为元素设置多组样式而不产生任何动画。但若不加进一步修饰,按钮会瞬间变换,没有淡入淡出、没有缩小,也没有任何视觉提示表明正在发生变化。

为了让变化不那么突兀,需要定义一个动画过渡(transition),以指定一段时间内从一个状态到另一个状态所发生的变化。transition() 函数接收两个参数:第一个参数是定义两个过渡状态之间方向的表达式,第二个参数接受一个或一系列 animate() 步骤。

使用 animate() 函数定义过渡的长度、延迟与缓动,并指定过渡期间使用的样式函数;animate() 还承载用于多步动画的 keyframes() 函数定义,这些定义放在 animate() 的第二个参数中。

动画元数据:时长、延迟与缓动

animate() 函数(过渡函数的第二个参数)接受 timingsstyles 两个输入参数。timings 参数可以是一个数字,或一个由三部分组成的字符串:

animate(duration);

或者

animate('duration delay easing');
  • 第一部分 duration(时长):必填。时长可以是无引号的数字(表示毫秒),或带引号加时间后缀的秒数。例如,十分之一秒可表示为:
    • 纯数字(毫秒):100
    • 字符串(毫秒):'100ms'
    • 字符串(秒):'0.1s'
  • 第二部分 delay(延迟):语法与 duration 相同。例如"等待 100ms 后运行 200ms":'0.2s 100ms'
  • 第三部分 easing(缓动):控制动画在其运行期间的加速与减速方式。例如 ease-in 会让动画开始时慢、随后逐渐加速。
    • 等待 100ms 运行 200ms,用减速曲线,先快后慢最终平稳停靠:'0.2s 100ms ease-out'
    • 运行 200ms 无延迟,用标准曲线,先慢、中间加速、末端减速:'0.2s ease-in-out'
    • 立即开始运行 200ms,用加速曲线,先慢最后达到全速:'0.2s ease-in'

下面这个例子提供了一个从 openclosed 的 1 秒状态过渡:

transition('open => closed', [animate('1s')]),

再补充一个从 closedopen 的 0.5 秒过渡:

transition('closed => open', [animate('0.5s')]),

要点:代码中 => 运算符表示单向过渡,<=> 表示双向。在过渡内部,animate() 指定过渡耗时,此处 openclosed 的变化为 1 秒(写作 1s)。

关于 state()transition() 中样式的额外说明:

  • state() 定义在每次过渡结束时应用的样式——它们在动画完成后仍然保留;
  • transition() 定义中间样式,在动画过程中营造运动错觉;
  • 当动画被禁用时,transition() 的样式可被跳过,但 state() 的样式不能;
  • 可以在同一个 transition() 参数中包含多个状态对,例如:transition('on => off, off => void');

触发动画

动画需要一个触发器(trigger),这样它才知道何时开始。trigger() 函数收集状态与过渡,并为动画命名,以便在 HTML 模板中将其附加到触发元素上。trigger() 描述需要监视变化的属性名;当变化发生时,触发器会启动其定义中包含的动作——这些动作可以是过渡,也可以是后面会看到的其他函数。

在本例中,将触发器命名为 openClose 并附加到 button 元素上,它描述 openclosed 两种状态以及两个过渡的时序。

提示:在每个 trigger() 函数调用内部,一个元素在任意时刻只能处于一个状态;但同时可以有多个触发器处于激活状态。

定义动画并绑定到 HTML 模板

动画定义在控制待动画 HTML 元素的那个组件的元数据中。将定义动画的代码放在 @Component() 装饰器的 animations: 属性下。完整的 open-close 组件定义如下(取自 open-close.ts):

@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      state('open', style({
        height: '200px',
        opacity: 1,
        backgroundColor: 'yellow',
      })),
      state('closed', style({
        height: '100px',
        opacity: 0.8,
        backgroundColor: 'blue',
      })),
      transition('open => closed', [animate('1s')]),
      transition('closed => open', [animate('0.5s')]),
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }
}

为组件定义了动画触发器后,需在模板中通过将触发器名包裹在方括号内、前面加 @ 符号的方式附加到元素上。然后,你可以用标准 Angular 属性绑定语法将触发器绑定到一个模板表达式,其中 triggerName 是触发器名,expression 会求值为一个已定义的动画状态:

<div [@triggerName]="expression">…</div>

当表达式值变化到新的状态时,动画被执行或触发。下面这个片段将触发器绑定到 isOpen 属性的值(取自 open-close.1.html):

<nav>
  <button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>

isOpen 表达式求值为已定义的 openclosed 状态时,它会通知 openClose 触发器状态发生了变化,随后交由 openClose 代码处理该变化并启动状态变更动画。

对于进入或离开页面(即被插入或移出 DOM)的元素,你可以让动画有条件地执行。例如,在 HTML 模板中将 *ngIf 与动画触发器一起使用。

提示:在组件文件中,将定义动画的触发器设为 @Component() 装饰器里 animations: 属性的值;在 HTML 模板文件中,用触发器名将定义好的动画附加到待动画的 HTML 元素上。

完整代码回顾

以下是本过渡示例涉及的全部代码文件。组件与模板如上所示,配套的样式 open-close.css 定义了容器边框、内边距与字体等静态外观:

:host {
  display: block;
  margin-top: 1rem;
}

.open-close-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  color: #000000;
  font-weight: bold;
  font-size: 20px;
}

小结

你已学会用 style()state() 配合 animate() 的时序控制,为两个状态之间的过渡添加动画。更进阶的特性(复杂序列、可复用动画、路由过渡动画)可在后续的过渡与触发器、复杂序列等章节中继续学习。

动画 API 函数速查表

@angular/animations 模块提供的函数式 API 是创建和控制 Angular 应用动画的一种领域特定语言(DSL)。下表列出核心函数及其作用:

函数名 作用
trigger() 启动动画,充当所有其他动画函数调用的容器。HTML 模板绑定到 triggerName。用第一个参数声明唯一的触发器名。使用数组语法。
style() 定义动画中要使用的一个或多个 CSS 样式。控制 HTML 元素在动画过程中的视觉外观。使用对象语法。
state() 创建一组命名的 CSS 样式,在成功过渡到某个状态时应用。该状态之后可被其他动画函数按名称引用。
animate() 指定过渡的时序信息。delayeasing 为可选值。内部可包含 style() 调用。
transition() 定义两个命名状态之间的动画序列。使用数组语法。
keyframes() 允许在指定时间区间内对样式进行顺序变化。在 animate() 内使用。每个 keyframe() 内可包含多个 style() 调用。使用数组语法。
group() 指定一组并行执行的动画步骤(内部动画)。只有当所有内部动画步骤都完成后动画才继续。在 sequence()transition() 内使用。
query() 在当前元素内查找一个或多个内部 HTML 元素。
sequence() 指定一个逐个顺序执行的动画步骤列表。
stagger() 为多个元素的动画错开其开始时间。
animation() 生成一个可在别处调用的可复用动画。与 useAnimation() 一起使用。
useAnimation() 激活一个可复用动画。与 animation() 一起使用。
animateChild() 允许子组件上的动画在父组件的同一时间范围内运行。

从源码结构看,上表中的每一个函数都在 packages/animations/src/animations.ts 中被统一导出,与该文件的 export 列表一一对应,这为理解各函数在编译与运行时的实际来源提供了直接依据。

深入仓库:动画包的组织结构

为进一步理解这套 API 在仓库中如何落地,可关注 packages/animations/src/ 目录下的几个关键文件:

  • animations.ts:公共 API 入口,统一导出全部动画函数与元数据类型;
  • animation_metadata.ts:定义 triggerstatetransitionanimatekeyframesstyle 等函数以及对应的 Animation*Metadata 数据结构,即 DSL 的"词汇表"来源;
  • animation_builder.ts:导出 AnimationBuilderAnimationFactory,负责将元数据构建为可执行的动画工厂;
  • animation_event.ts:定义 AnimationEvent,对应模板中 (@openClose.done)(@openClose.start) 等事件监听所接收的对象;
  • players/ 目录:存放不同渲染环境下的播放器实现。

这一结构表明:你在组件 animations: 属性中写的 trigger/state/transition 等调用,本质是构造一组"动画元数据"对象,再由构建器在运行时转化为对浏览器 CSS/JS 动画引擎的驱动。理解这条"元数据 → 构建 → 播放"的链路,有助于在调试动画不生效或时序异常时定位问题层次。

相关进阶主题

本文覆盖了帮助你开始为项目添加 Angular 动画的基础功能。若需继续深入,仓库文档中还提供了以下进阶指南,均位于 guide/animations 目录下:

适用前提:本文所有代码示例与 API 说明均以当前仓库 packages/animations/ 包及 adev/src/content/examples/animations/ 目录下示例的实际内容为准。由于 @angular/animations 已被标记弃用,新建项目应优先考虑原生 CSS 动画方案;本文的价值在于理解存量应用中最广泛使用的传统动画 API,为阅读、维护与迁移这类代码提供基础。

登录后查看全文
热门项目推荐
相关项目推荐