angular示例
Modern web components frequently use animations. Cascading Style-sheets (CSS) arms developers with the tools to create impressive animations. Property transitions, uniquely named animations, multi-part keyframes are possible with CSS. The animatable possibilities are endless thanks to CSS.
现代的Web组件经常使用动画。 级联样式表(CSS)为开发人员提供了创建令人印象深刻的动画的工具。 使用CSS可以进行属性转换,唯一命名的动画,多部分关键帧。 借助CSS,可实现动画的可能性无穷无尽。
In a modern web application, animation focuses the user’s attention. Good animations seek to guide the user’s attention in a satisfying, productive manner. Animations should not prove annoying to the user.
在现代的Web应用程序中,动画会吸引用户的注意力。 好的动画试图以令人满意的,富有成效的方式引导用户的注意力。 动画不应对用户造成困扰。
Animations offer feedback in the form of movement. They show the user that the application is actively handling their requests. Something as simple as a visible button press or a loader when the application must load engages the user’s attention.
动画以运动形式提供反馈。 它们向用户显示该应用程序正在积极处理他们的请求。 当必须加载应用程序时,诸如可见按钮按下或加载程序之类的简单操作就引起了用户的注意。
Animations continue to grow more and more relevant in Angular’s case. Google develops Angular while promoting the Material Design philosophy. It encourages concise user interfaces (UI) supplemented with animated user feedback. It makes web applications feel somewhat alive and fun to use.
在Angular的案例中,动画的重要性越来越高。 Google在推广材料设计理念的同时开发了Angular。 它鼓励简洁的用户界面(UI),并带有动画用户反馈。 它使Web应用程序感到有些活跃和有趣。
The Angular community develops a core widget library called Material2. This project adds a variety of widget modules to Angular. Most of them feature animations. To understand how they work, this article recommends studying CSS animations before reading on.
Angular社区开发了一个名为Material2的核心小部件库。 该项目向Angular添加了各种小部件模块。 它们大多数具有动画。 为了理解它们的工作原理,本文建议在继续阅读之前研究CSS动画。
Angular animations is the framework’s streamlined version of what CSS natively provides. CSS is the core technology for Angular animations occurring within the web browser. CSS is beyond the scope of this article though. Its time to tackle Angular animations head-on.
角动画是CSS原生提供的框架的简化版本。 CSS是Web浏览器中发生的Angular动画的核心技术。 CSS超出了本文的范围。 是时候直接解决Angular动画了。
Before animating, the BrowserAnimationsModule
must include into the root module’s imports array. It is available from @angular/platform-browser/animations
. This NgModule ensures animations work for the given platform. This article assumes the standard web browser for each example.
设置动画之前, BrowserAnimationsModule
必须包含在根模块的imports数组中。 可从@angular/platform-browser/animations
。 此NgModule可确保动画在给定平台上正常工作。 本文假定每个示例都使用标准的Web浏览器。
Angular animations declare within the @Component
metadata. @Component
decorates a class to distinguish it as a component to Angular. Its metadata contains component configurations including the animations: []
field. Each array element from this field represents an animation trigger (AnimationTriggerMetadata
).
角动画在@Component
元数据中声明。 @Component
装饰一个类以将其区分为Angular的组件。 它的元数据包含组件配置,其中包括animations: []
字段。 此字段中的每个数组元素都表示一个动画触发器( AnimationTriggerMetadata
)。
Animations are exclusive to their host component via the decorator’s metadata. Animations can only be used in the host component’s template. Animations do not inherit to the component’s children. There is an easy work-around for this.
动画通过装饰器的元数据对其宿主组件具有独占性。 动画只能在宿主组件的模板中使用。 动画不会继承到组件的子代。 有一个简单的解决方法。
You could always create a separate file that exports an array. Any component class can import that array from the top of its host file. The imported array token then goes into the animations metadata of the component. Repeat this process for any other components requiring the same array in their animations metadata.
您总是可以创建一个单独的文件来导出数组。 任何组件类都可以从其主机文件的顶部导入该数组。 然后,导入的数组标记将进入组件的动画元数据。 对在其动画元数据中需要相同数组的任何其他组件重复此过程。
Content projection lets you apply animations to component A’s content DOM (Document Object Model). Component B wrapping this content DOM can project the contents into its own template. Once it does, the animations of component A do not negate. Component B incorporates A’s animations through content projection.
内容投影使您可以将动画应用于组件A的内容DOM(文档对象模型)。 包装此内容DOM的组件B可以将内容投影到其自己的模板中。 一旦完成,组件A的动画就不会否定。 组件B通过内容投影合并了A的动画。
OK. You know how to setup animations and where to declare them. Implementation is the next step.
好。 您知道如何设置动画以及在哪里声明它们。 实施是下一步。
Angular animations use a series of method calls importable from @angular/animations
. Each element of the @Component
animations array begins as a single method. Its arguments unravel as a sequence of higher-order method calls. The following list shows some of the methods used to build Angular animations.
角动画使用一系列可从@angular/animations
导入的方法调用。 @Component
animations数组的每个元素都以单个方法开始。 它的参数分解为一系列高阶方法调用。 以下列表显示了一些用于构建Angular动画的方法。
trigger(selector: string, AnimationMetadata[])
trigger(selector: string, AnimationMetadata[])
returns AnimationTriggerMetadata
返回AnimationTriggerMetadata
state(data: string, AnimationStyleMetadata, options?: object)
state(data: string, AnimationStyleMetadata, options?: object)
returns AnimationStateMetadata
返回AnimationStateMetadata
style(CSSKeyValues: object)
style(CSSKeyValues: object)
returns AnimationStyleMetadata
返回AnimationStyleMetadata
animate(timing: string|number, AnimationStyleMetadata|KeyframesMetadata)
animate(timing: string|number, AnimationStyleMetadata|KeyframesMetadata)
returns AnimationAnimateMetadata
返回AnimationAnimateMetadata
transition(stateChange: string, AnimationMetadata|AnimationMetadata[], options?: object)
transition(stateChange: string, AnimationMetadata|AnimationMetadata[], options?: object)
returns AnimationTransitionMetadata
返回AnimationTransitionMetadata
While there are certainly more methods to pick from, these five methods handle the basics. Trying to understand these core methods as a list does not help very much. Bullet-by-bullet explanations followed by an example will make better sense of it.
虽然肯定有更多方法可供选择,但这五种方法可以处理基础知识。 尝试将这些核心方法理解为列表没有太大帮助。 逐个子弹的解释以及后面的示例将使它更好地理解。
The trigger(...)
method encapsulates a single element of animation inside the animations array.
trigger(...)
方法将单个动画元素封装在animations数组内。
The method’s first argument selector: string
matches the [@selector]
member attribute. It acts like an attribute directive in the component template. It essentially connects the animation element to the template through an attribute selector.
方法的第一个参数selector: string
与[@selector]
成员属性匹配。 它就像组件模板中的属性指令一样。 它实质上是通过属性选择器将动画元素连接到模板。
The second argument is an array containing a list of applicable animation methods. The trigger(...)
keeps it altogether in a single array.
第二个参数是一个数组,其中包含适用的动画方法列表。 trigger(...)
将其完全保留在单个数组中。
The state(...)
method defines the final state of the animation. It applies a list of CSS properties to the target element after an animation concludes. This is so the animated element’s CSS matches the animation’s resolution.
state(...)
方法定义动画的最终状态。 动画结束后,它将CSS属性列表应用于目标元素。 因此,动画元素CSS与动画的分辨率匹配。
The first argument matches the value of the data bound to the animation binding. That is, the value bound to [@selector]
in the template matches against first argument of a state(...)
. The data’s value determines the final state. The changing of the value determines the means of animation (see transition(...)
).
第一个参数与绑定到动画绑定的数据的值匹配。 也就是说,模板中绑定到[@selector]
的值与state(...)
第一个参数匹配。 数据的值确定最终状态。 值的更改确定动画的方式(请参见transition(...)
)。
The second argument hosts the CSS styles that apply to an element post-animation. Styles get passed in by invoking style(...)
and passing into its argument the desired styles as an object.
第二个参数包含适用于元素动画后CSS样式。 通过调用style(...)
并将所需的样式作为对象传递到其参数中来传递样式。
A list of options optionally occupies the third argument. The default state(...)
options should remain unchanged unless reasoned otherwise.
选项列表可选地占据第三个参数。 除非另有说明,否则默认的state(...)
选项应保持不变。
You may have noticed AnimationStyleMetadata
several times in the previous list. The style(...)
component returns this exact type of metadata. Wherever CSS styles apply, the style(...)
method must invoke. An object containing CSS styles stands-in for its argument.
您可能已经在上一个列表中多次注意到AnimationStyleMetadata
。 style(...)
组件返回此确切类型的元数据。 无论在哪里应用CSS样式,都必须调用style(...)
方法。 包含CSS样式的对象代表其参数。
Of course, styles animatable in CSS carry over into the Angular style(...)
method. Granted, nothing impossible for CSS becomes suddenly possible with Angular animations.
当然,CSS中可设置动画的样式会延续到Angular style(...)
方法中。 当然,使用Angular动画,CSS不可能实现的一切突然变得不可能。
The animate(...)
function accepts a timing expression as its first argument. This argument times, paces, and/or delays the method’s animation. This argument accepts either a number or string expression. The formatting is explained here.
animate(...)
函数接受一个计时表达式作为其第一个参数。 该参数会设置时间,节奏和/或延迟该方法的动画。 此参数接受数字或字符串表达式。 此处说明了格式。
The second argument of animate(...)
is the CSS property warranting the animation. This takes the form of the style(...)
method which returns AnimationStyleMetadata
. Think of animate(...)
as the method that initiates the animation.
animate(...)
的第二个参数是保证动画CSS属性。 这采用style(...)
方法的形式,该方法返回AnimationStyleMetadata
。 将animate(...)
视为启动动画的方法。
A series of keyframes can also apply to the second argument. Keyframes is a more advanced option that this article explains later on. Keyframes distinguish various sections of the animation.
一系列关键帧也可以应用于第二个参数。 关键帧是本文稍后介绍的更高级的选项。 关键帧可区分动画的各个部分。
animate(...)
may not receive a second argument. In that case, the method’s animation timing only applies to the CSS reflected in the state(...)
methods. Property changes in the trigger’s state(...)
methods will animate.
animate(...)
可能不会收到第二个参数。 在这种情况下,该方法的动画计时仅适用于state(...)
方法中反映CSS。 触发器的state(...)
方法中的属性更改将进行动画处理。
animate(...)
initiates an animation while transition(...)
determines which animation initiates.
animate(...)
启动动画,而transition(...)
确定启动哪个动画。
The first argument consists of a unique form of micro-syntax. It denotes a change in state (or change in data) taking place. The data bound to the template animation binding ([selector]="value"
) determines this expression. The upcoming section titled “Animation State” explains this concept a bit further.
第一个参数由微句法的一种独特形式组成。 它表示状态发生改变(或数据改变)。 绑定到模板动画绑定的数据( [selector]="value"
)确定此表达式。 接下来的标题为“动画状态”的部分进一步解释了此概念。
The second argument of transition(...)
comprises AnimationMetadata
(returned by animate(...)
). The argument accepts either an array of AnimationMetadata
or a single instance.
transition(...)
的第二个参数包括AnimationMetadata
(由animate(...)
返回)。 该参数接受AnimationMetadata
数组或单个实例。
The first argument’s value matches against the value of the data bound in the template ([selector]="value"
) . If a perfect match occurs, the argument evaluates successfully. The second argument then initiates an animation in response to the success of the first.
第一个参数的值与模板中绑定的数据的值( [selector]="value"
)相匹配。 如果出现完美匹配,则参数将成功求值。 然后,第二个参数响应第一个参数的成功启动动画。
A list of options optionally occupies the third argument. The default transition(...)
options should remain unchanged unless reasoned otherwise.
选项列表可选地占据第三个参数。 除非另有说明,否则默认的transition(...)
选项应保持不变。
import { Component, OnInit } from '@angular/core';
import { trigger, state, style, animate, transition } from '@angular/animations';
@Component({
selector: 'app-example',
template: `
<h3>Click the button to change its color!</h3>
<button (click)="toggleIsCorrect()" // event binding
[@toggleClick]="isGreen">Toggle Me!</button> // animation binding
`,
animations: [ // metadata array
trigger('toggleClick', [ // trigger block
state('true', style({ // final CSS following animation
backgroundColor: 'green'
})),
state('false', style({
backgroundColor: 'red'
})),
transition('true => false', animate('1000ms linear')), // animation timing
transition('false => true', animate('1000ms linear'))
])
] // end of trigger block
})
export class ExampleComponent {
isGreen: string = 'true';
toggleIsCorrect() {
this.isGreen = this.isGreen === 'true' ? 'false' : 'true'; // change in data-bound value
}
}
The above example performs a very simple color swap with each button click. Of course, the color transitions quickly in a linear fade as per animate('1000ms linear')
. The animation binds to the button by matching the first argument of trigger(...)
to the [@toggleClick]
animation binding.
上面的示例在每次单击按钮时都执行了非常简单的颜色交换。 当然,每个animate('1000ms linear')
的颜色会以线性淡入淡出快速过渡animate('1000ms linear')
。 通过将trigger(...)
的第一个参数与[@toggleClick]
动画绑定匹配,动画将绑定到按钮。
The binding binds to the value of isGreen
from the component class. This value determines the resulting color as set by the two style(...)
methods inside the trigger(...)
block. The animation binding is one-way so that changes to isGreen
in the component class notify the template binding. That is, the animation binding [@toggleClick]
.
绑定绑定到组件类中isGreen
的值。 该值决定了trigger(...)
块中的两个style(...)
方法所设置的结果颜色。 动画绑定是单向的,因此组件类中对isGreen
更改将通知模板绑定。 即,动画绑定[@toggleClick]
。
The button element in the template also has a click
event bound to it. Clicking the button causes isGreen
to toggle values. This changes the component class data. The animation binding picks up on this and invokes its matching trigger(...)
method. The trigger(...)
lies within the animations array of the component’s metadata. Two things occur upon the trigger’s invocation.
模板中的button元素还绑定有click
事件。 单击按钮会导致isGreen
切换值。 这将更改组件类数据。 动画绑定对此进行拾取,并调用其匹配的trigger(...)
方法。 trigger(...)
位于组件元数据的animations数组内。 触发触发器时会发生两件事。
The first occurrence concerns the two state(...)
methods. The new value of isGreen
matches against a state(...)
method’s first argument. Once it matches, the CSS styles of style(...)
apply to the final state of the animation binding’s host element. `The final state takes effect following all animation.
第一次出现涉及两个state(...)
方法。 isGreen
的新值与state(...)
方法的第一个参数匹配。 一旦匹配, style(...)
CSS样式将应用于动画绑定的宿主元素的最终状态。 `最终状态在所有动画之后生效。
Now for the second occurrence. The data change that invoked the animation binding compares across the two transition(...)
methods. One of them matches the change in data to their first argument. The first button click caused isGreen
to go from ‘true’ to ‘false’ (‘true => false’). That means the first transition(...)
method activates its second argument.
现在第二次出现。 调用动画绑定的数据更改在两个transition(...)
方法之间进行比较。 其中一个将数据更改与第一个参数匹配。 第一次单击导致isGreen
从“ true”变为“ false”(“ true => false”)。 这意味着第一个transition(...)
方法将激活其第二个参数。
The animate(...)
function corresponding the successfully evaluated transition(...)
method initiates. This method sets the duration of the animated color fade along with the fade’s pacing. The animation executes and the button fades to red.
将启动与成功评估的transition(...)
方法相对应的animate(...)
函数。 此方法设置动画颜色淡入淡出的持续时间以及淡入淡出的步调。 动画将执行,并且按钮将变为红色。
This process can happen any number of times following a button click. The backgroundColor
of the button will cycle between green and red in a linear fade.
单击按钮后,此过程可以发生任意多次。 按钮的backgroundColor
将以线性淡入淡出在绿色和红色之间循环。
The transition(...)
micro-syntax is worth addressing in detail. Angular determines animations and their timing by evaluating this syntax. There exists the following state transitions. They model a changes in data bound to an animation binding.
transition(...)
微语法值得详细解决。 Angular通过评估此语法来确定动画及其定时。 存在以下状态转换。 他们为绑定到动画绑定的数据更改建模。
‘someValue’ => ‘anotherValue’
'someValue' => 'anotherValue'
An animation trigger where the bound data changes from ‘someValue’ to ‘anotherValue’.
动画触发器,其中绑定数据从“ someValue”更改为“ anotherValue”。
‘anotherValue’ => ‘someValue’
'anotherValue' => 'someValue'
An animation trigger where the bound data changes from ‘anotherValue’ to ‘someValue’.
动画触发器,其中绑定数据从“ anotherValue”更改为“ someValue”。
‘someValue’ <=> ‘anotherValue’
'someValue' <=> 'anotherValue'
Data changes from ‘someValue` to ‘anotherValue’ or vice versa.
数据从“ someValue”更改为“ anotherValue”,反之亦然。
There also exists void
and *
states. void
indicates that the component is either entering or leaving the DOM. This is perfect for entry and exit animations.
也存在void
和*
状态。 void
指示组件正在进入或离开DOM。 这非常适合进入和退出动画。
‘someValue’ => void
: host component of bound data is leaving the DOM
'someValue' => void
:绑定数据的主机组件正在离开 DOM
void => ‘someValue’
: host component of bound data is entering the DOM
void => 'someValue'
:绑定数据的主机组件正在进入 DOM
*
denotes a wildcard state. Wildcard states can interpret to “any state”. This includes void
plus any other change to the bound data.
*
表示通配符状态。 通配符状态可以解释为“任何状态”。 这包括void
以及对绑定数据的任何其他更改。
This article touched on the basics for animating Angular applications. Advanced animation techniques exist alongside these basics. Grouping together keyframes is one such technique. Its inspired from the @keyframes
CSS rule. If you have worked with CSS @keyframes
, you already understand how keyframes in Angular work. It becomes just a matter of syntax
本文介绍了Angular应用程序动画的基础知识。 这些基本知识与高级动画技术并存。 将关键帧分组在一起就是这样一种技术。 它的灵感来自@keyframes
CSS规则。 如果您使用过CSS @keyframes
,那么您已经了解了Angular中的关键帧如何工作。 这只是语法问题
The keyframes(...)
method imports from @angular/animations
. It passes into the second argument of animate(...)
instead of the typical AnimationStyleMetadata
. The keyframes(...)
method accepts one argument as an array of AnimationStyleMetadata
. This can also be referred to as an array of style(...)
methods.
keyframes(...)
方法从@angular/animations
导入。 它传递给animate(...)
的第二个参数,而不是典型的AnimationStyleMetadata
。 keyframes(...)
方法接受一个参数作为AnimationStyleMetadata
的数组。 这也可以称为style(...)
方法的数组。
Each keyframe of the animation goes inside the keyframes(...)
array. These keyframe elements are style(...)
methods supporting the offset
property. offset
indicates a point in the animation’s duration where its accompanying style properties should apply. Its value spans from 0 (animation start) to 1 (animation end).
动画的每个关键帧都位于keyframes(...)
数组内。 这些关键帧元素是支持offset
属性的style(...)
方法。 offset
指示动画持续时间中应应用其伴随样式属性的一点。 其值的范围从0(动画开始)到1(动画结束)。
import { Component } from '@angular/core';
import { trigger, state, style, animate, transition, keyframes } from '@angular/animations';
@Component({
selector: 'app-example',
styles: [
`.ball {
position: relative;
background-color: black;
border-radius: 50%;
top: 200px;
height: 25px;
width: 25px;
}`
],
template: `
<h3>Arcing Ball Animation</h3>
<button (click)="toggleBounce()">Arc the Ball!</button>
<div [@animateArc]="arc" class="ball"></div>
`,
animations: [
trigger('animateArc', [
state('true', style({
left: '400px',
top: '200px'
})),
state('false', style({
left: '0',
top: '200px'
})),
transition('false => true', animate('1000ms linear', keyframes([
style({ left: '0', top: '200px', offset: 0 }),
style({ left: '200px', top: '100px', offset: 0.50 }),
style({ left: '400px', top: '200px', offset: 1 })
]))),
transition('true => false', animate('1000ms linear', keyframes([
style({ left: '400px', top: '200px', offset: 0 }),
style({ left: '200px', top: '100px', offset: 0.50 }),
style({ left: '0', top: '200px', offset: 1 })
])))
])
]
})
export class ExampleComponent {
arc: string = 'false';
toggleBounce(){
this.arc = this.arc === 'false' ? 'true' : 'false';
}
}
The main difference of the above example compared to the other example is the second argument of animate(...)
. It now contains a keyframes(...)
method hosting an array of animation keyframes. While the animation itself is also different, the technique to animate is similar.
上面的示例与另一个示例相比,主要区别在于animate(...)
的第二个参数。 现在,它包含一个keyframes(...)
方法,该方法承载着一系列动画关键帧。 尽管动画本身也不同,但是动画制作技术却相似。
Clicking the button causes the button to arc across the screen. The arc moves as per the keyframes(...)
method’s array elements (keyframes). At the animation’s mid-point (offset: 0.50
), the ball changes trajectory. It descends to its original height as it continues across the screen. Clicking the button again reverses the animation.
单击按钮会使按钮在屏幕上成弧形。 圆弧按照keyframes(...)
方法的数组元素(关键帧)移动。 在动画的中点( offset: 0.50
)处,球会改变轨迹。 随着它在屏幕上继续前进,它下降到其原始高度。 再次单击该按钮将反转动画。
left
and top
are animatable properties after setting position: relative;
for the element. The transform
property can perform similar movement-based animations. transform
is an expansive yet fully animatable property.
left
和top
是设置position: relative;
后的动画属性position: relative;
为元素。 transform
属性可以执行类似的基于运动的动画。 transform
是可扩展但完全可动画的属性。
Any number of keyframes can existing between offset 0 and 1. Intricate animation sequences take the form of keyframes. They are one of many advanced techniques in Angular animations.
偏移量0和1之间可以存在任意数量的关键帧。复杂的动画序列采用关键帧的形式。 它们是Angular动画中许多高级技术之一。
You will undoubtedly come across the situation where you want to attach an animation to the HTML element of a component itself, instead of an element in the component’s template. This requires a little more effort since you can’t just go into the template HTML and attach the animation there. Instead, you’ll have to import HostBinding
and utilize that.
毫无疑问,您会遇到想要将动画附加到组件本身HTML元素而不是组件模板中的元素的情况。 这需要更多的精力,因为您不能只进入模板HTML并在其中附加动画。 相反,您必须导入HostBinding
并加以利用。
The minimal code for this scenario is shown below. I’ll re-use the same animation condition for the code above for consistency and I don’t show any of the actual animation code since you can easily find that above.
下面显示了此方案的最小代码。 为了保持一致性,我将对上面的代码重新使用相同的动画条件,并且由于您可以轻松地在上面找到代码,因此我不显示任何实际的动画代码。
import { Component, HostBinding } from '@angular/core';
@Component({
...
})
export class ExampleComponent {
@HostBinding('@animateArc') get arcAnimation() {
return this.arc;
}
}
The idea behind animating the host component is pretty much the same as animating a element from the template with the only difference being your lack of access to the element you are animating. You still have to pass the name of the animation (@animateArc
) when declaring the HostBinding
and you still have to return the current state of the animation (this.arc
). The name of the function doesn’t actual matter, so arcAnimation
could have been changed to anything, as long as it doesn’t clash with existing property names on the component, and it would work perfectly fine.
对宿主组件进行动画处理的想法与从模板对元素进行动画处理几乎相同,唯一的区别是您无法访问要进行动画处理的元素。 在声明HostBinding
,您仍然必须传递动画的名称( @animateArc
),并且仍然必须返回动画的当前状态( this.arc
)。 函数的名称并不重要,因此只要不与组件上的现有属性名称冲突, arcAnimation
可以更改为任何内容,并且可以很好地工作。
This covers the basics of animating with Angular. Angular makes setting up animations very easy using the Angular CLI. Getting started with your first animation only requires a single component class. Remember, animations scope to the component’s template. Export your transitions array from a separate file if you plan to use it across multiple components.
这涵盖了使用Angular进行动画制作的基础知识。 Angular使使用Angular CLI设置动画非常容易。 第一个动画入门仅需要单个组件类。 请记住,动画的作用域是组件的模板。 如果计划跨多个组件使用过渡数组,请从单独的文件中导出过渡数组。
Every animation utility/method exports from @angular/animations
. They all work together to provide a robust system of animation inspired from CSS. There are more methods beyond what this article could cover.
每个动画实用程序/方法都从@angular/animations
导出。 它们一起工作以提供一个受CSS启发的强大动画系统。 除了本文可以涵盖的范围之外,还有更多方法。
Now that you know the basics, feel free to explore the links below for more on Angular animations.
现在,您已经了解了基础知识,可以随时浏览以下链接,以获取有关Angular动画的更多信息。
翻译自: https://www.freecodecamp.org/news/angular-animations-explained-with-examples/
angular示例