Angular 动画入门指南:用 @angular/animations 构建状态过渡动画
本文以 Angular 官方动画入门指南为主体,完整讲解如何使用 @angular/animations 包定义动画状态、配置过渡时长与缓动、将触发器绑定到模板,并从一个可运行的 open-close 按钮动画示例出发,逐层剖析 trigger()、state()、style()、animate()、transition() 等核心函数的协作机制。读完你能掌握 Angular 动画的完整接入流程、时序参数语法,并能对照仓库源码理解各函数的真实定义与导出路径。
重要提示:
@angular/animations包现已弃用(deprecated)。Angular 团队建议使用原生 CSS 配合animate.enter与animate.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 入口,它统一导出了 animate、animateChild、animation、group、keyframes、query、sequence、stagger、state、style、transition、trigger、useAnimation 等全部动画函数及相关元数据类型(如 AnimationStateMetadata、AnimationTriggerMetadata、AnimationAnimateMetadata)。这意味着上一步中 from '@angular/animations' 导入的每一个符号,都对应包内一处真实的实现导出。
添加动画元数据属性
在组件文件中,于 @Component() 装饰器内添加一个名为 animations: 的元数据属性,将定义动画的触发器(trigger)放在该属性中。示例如下(取自 app.ts):
@Component({
selector: 'app-root',
templateUrl: 'app.html',
styleUrls: ['app.css'],
animations: [
// 动画触发器放在这里
],
})
动画一次状态过渡:open-close 示例
下面通过一个具体例子,把单个 HTML 元素从一种状态过渡到另一种状态。假设一个按钮根据用户最后动作显示 Open 或 Closed:处于 open 状态时,它可见且为黄色;处于 closed 状态时,它半透明且为蓝色。
先在终端运行以下命令生成组件:
ng g component open-close
这会在 src/app/open-close.ts 创建该组件。接下来分三步定义状态、过渡与触发器。
动画状态与样式
使用 state() 函数为每次过渡结束时调用定义不同的状态。它接收两个参数:一个唯一名称(如 open 或 closed)和一个 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() 函数(过渡函数的第二个参数)接受 timings 与 styles 两个输入参数。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'
- 等待 100ms 运行 200ms,用减速曲线,先快后慢最终平稳停靠:
下面这个例子提供了一个从 open 到 closed 的 1 秒状态过渡:
transition('open => closed', [animate('1s')]),
再补充一个从 closed 到 open 的 0.5 秒过渡:
transition('closed => open', [animate('0.5s')]),
要点:代码中
=>运算符表示单向过渡,<=>表示双向。在过渡内部,animate()指定过渡耗时,此处open到closed的变化为 1 秒(写作1s)。
关于 state() 与 transition() 中样式的额外说明:
- 用
state()定义在每次过渡结束时应用的样式——它们在动画完成后仍然保留; - 用
transition()定义中间样式,在动画过程中营造运动错觉; - 当动画被禁用时,
transition()的样式可被跳过,但state()的样式不能; - 可以在同一个
transition()参数中包含多个状态对,例如:transition('on => off, off => void');
触发动画
动画需要一个触发器(trigger),这样它才知道何时开始。trigger() 函数收集状态与过渡,并为动画命名,以便在 HTML 模板中将其附加到触发元素上。trigger() 描述需要监视变化的属性名;当变化发生时,触发器会启动其定义中包含的动作——这些动作可以是过渡,也可以是后面会看到的其他函数。
在本例中,将触发器命名为 openClose 并附加到 button 元素上,它描述 open 与 closed 两种状态以及两个过渡的时序。
提示:在每个
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 表达式求值为已定义的 open 或 closed 状态时,它会通知 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() |
指定过渡的时序信息。delay 与 easing 为可选值。内部可包含 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:定义
trigger、state、transition、animate、keyframes、style等函数以及对应的Animation*Metadata数据结构,即 DSL 的"词汇表"来源; - animation_builder.ts:导出
AnimationBuilder与AnimationFactory,负责将元数据构建为可执行的动画工厂; - animation_event.ts:定义
AnimationEvent,对应模板中(@openClose.done)、(@openClose.start)等事件监听所接收的对象; - players/ 目录:存放不同渲染环境下的播放器实现。
这一结构表明:你在组件 animations: 属性中写的 trigger/state/transition 等调用,本质是构造一组"动画元数据"对象,再由构建器在运行时转化为对浏览器 CSS/JS 动画引擎的驱动。理解这条"元数据 → 构建 → 播放"的链路,有助于在调试动画不生效或时序异常时定位问题层次。
相关进阶主题
本文覆盖了帮助你开始为项目添加 Angular 动画的基础功能。若需继续深入,仓库文档中还提供了以下进阶指南,均位于 guide/animations 目录下:
- 过渡与触发器进阶:更复杂的过渡表达式与触发器技巧;
- 复杂动画序列:组合
sequence、group、stagger、query编排序列; - 可复用动画:用
animation()与useAnimation()复用动画片段; - 迁移到原生 CSS 动画:由于
@angular/animations已弃用,指导如何迁移到基于animate.enter/animate.leave的原生 CSS 方案; - 进入与离开动画:新版推荐的元素进出动画写法。
适用前提:本文所有代码示例与 API 说明均以当前仓库
packages/animations/包及 adev/src/content/examples/animations/ 目录下示例的实际内容为准。由于@angular/animations已被标记弃用,新建项目应优先考虑原生 CSS 动画方案;本文的价值在于理解存量应用中最广泛使用的传统动画 API,为阅读、维护与迁移这类代码提供基础。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00