docs: add guide for using native CSS animations (#60984)

This adds a guide for how to switch from the
angular animations package over to native CSS
animations.

PR Close #60984
This commit is contained in:
Jessica Janiuk
2025-03-26 13:36:15 -04:00
committed by Andrew Kushnir
parent f580318411
commit f054c00e54
48 changed files with 1271 additions and 3 deletions
+18 -3
View File
@@ -583,6 +583,21 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
},
],
},
{
label: 'Animations',
children: [
{
label: 'Animating your content',
path: 'guide/animations/css',
contentPath: 'guide/animations/css',
},
{
label: 'Route transition animations',
path: 'guide/animations/route-animations',
contentPath: 'guide/animations/route-animations',
},
],
},
],
},
{
@@ -788,9 +803,9 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
contentPath: 'guide/animations/reusable-animations',
},
{
label: 'Route transition animations',
path: 'guide/animations/route-animations',
contentPath: 'guide/animations/route-animations',
label: 'Migrating to Native CSS Animations',
path: 'guide/animations/migration',
contentPath: 'guide/animations/migration',
},
],
},
@@ -0,0 +1,13 @@
.container {
display: block;
overflow: hidden;
}
.container .content {
padding: 20px;
margin-top: 1em;
font-weight: bold;
font-size: 20px;
background-color: blue;
color: #ebebeb;
}
@@ -0,0 +1,10 @@
<!-- #docplaster -->
<h2>Auto Height Example</h2>
<button type="button" (click)="toggle()">Toggle Open/Close</button>
<div class="container" [@openClose]="isOpen() ? true : false">
<div class="content">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
</div>
@@ -0,0 +1,22 @@
import {Component, signal} from '@angular/core';
import {trigger, transition, state, animate, style} from '@angular/animations';
@Component({
selector: 'app-open-close',
animations: [
trigger('openClose', [
state('true', style({height: '*'})),
state('false', style({height: '0px'})),
transition('false <=> true', animate(1000)),
]),
],
templateUrl: 'auto-height.component.html',
styleUrl: 'auto-height.component.css',
})
export class AutoHeightComponent {
isOpen = signal(false);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
@@ -0,0 +1,33 @@
:host {
display: block;
font-size: 32px;
margin: 20px;
text-align: center;
}
section {
border: 1px solid lightgray;
border-radius: 50px;
}
p {
display: inline-block;
margin: 2rem 0;
text-transform: uppercase;
}
.controls {
padding-bottom: 2rem;
}
button {
font: inherit;
border: 0;
background: lightgray;
width: 50px;
border-radius: 10px;
}
button + button {
margin-left: 10px;
}
@@ -0,0 +1,8 @@
<h3>Increment and Decrement Example</h3>
<section>
<p [@incrementAnimation]="num()">Number {{ num() }}</p>
<div class="controls">
<button type="button" (click)="modify(1)">+</button>
<button type="button" (click)="modify(-1)">-</button>
</div>
</section>
@@ -0,0 +1,27 @@
// #docplaster
// #docregion
import {Component, signal} from '@angular/core';
import {trigger, transition, animate, style, query, stagger} from '@angular/animations';
@Component({
selector: 'app-increment-decrement',
templateUrl: 'increment-decrement.component.html',
styleUrls: ['increment-decrement.component.css'],
animations: [
trigger('incrementAnimation', [
transition(':increment', [
animate('300ms ease-out', style({color: 'green', transform: 'scale(1.3, 1.2)'})),
]),
transition(':decrement', [
animate('300ms ease-out', style({color: 'red', transform: 'scale(0.8, 0.9)'})),
]),
]),
],
})
export class IncrementDecrementComponent {
num = signal(0);
modify(n: number) {
this.num.update((v) => (v += n));
}
}
@@ -0,0 +1,12 @@
:host {
display: block;
}
.insert-remove-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
}
@@ -0,0 +1,13 @@
<!-- #docplaster -->
<h2>Insert/Remove</h2>
<nav>
<button type="button" (click)="toggle()">Toggle Insert/Remove</button>
</nav>
@if (isShown) {
<div @myInsertRemoveTrigger class="insert-remove-container">
<p>The box is inserted</p>
</div>
}
@@ -0,0 +1,22 @@
// #docplaster
import {Component} from '@angular/core';
import {trigger, transition, animate, style} from '@angular/animations';
@Component({
selector: 'app-insert-remove',
animations: [
trigger('myInsertRemoveTrigger', [
transition(':enter', [style({opacity: 0}), animate('200ms', style({opacity: 1}))]),
transition(':leave', [animate('200ms', style({opacity: 0}))]),
]),
],
templateUrl: 'insert-remove.component.html',
styleUrls: ['insert-remove.component.css'],
})
export class InsertRemoveComponent {
isShown = false;
toggle() {
this.isShown = !this.isShown;
}
}
@@ -0,0 +1,13 @@
: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;
}
@@ -0,0 +1,8 @@
<!-- #docplaster -->
<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>
@@ -0,0 +1,47 @@
import {Component, signal} from '@angular/core';
import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';
@Component({
selector: 'app-open-close',
animations: [
trigger('openClose', [
state(
'open',
style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow',
}),
),
state(
'closed',
style({
height: '100px',
opacity: 0.5,
backgroundColor: 'green',
}),
),
// ...
transition('* => *', [
animate(
'1s',
keyframes([
style({opacity: 0.1, offset: 0.1}),
style({opacity: 0.6, offset: 0.2}),
style({opacity: 1, offset: 0.5}),
style({opacity: 0.2, offset: 0.7}),
]),
),
]),
]),
],
templateUrl: 'open-close.component.html',
styleUrl: 'open-close.component.css',
})
export class OpenCloseComponent {
isOpen = signal(false);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
@@ -0,0 +1,5 @@
.items {
list-style: none;
padding: 0;
margin: 0;
}
@@ -0,0 +1,9 @@
<!-- #docplaster -->
<h1>Reordering List Example</h1>
<button type="button" (click)="randomize()">Randomize</button>
<ul class="items">
@for(item of items; track item) {
<li @itemAnimation class="item">{{ item }}</li>
}
</ul>
@@ -0,0 +1,37 @@
// #docplaster
import {Component, signal} from '@angular/core';
import {trigger, transition, animate, query, style} from '@angular/animations';
@Component({
selector: 'app-reorder',
templateUrl: './reorder.component.html',
styleUrls: ['reorder.component.css'],
animations: [
trigger('itemAnimation', [
transition(':enter', [
style({opacity: 0, transform: 'translateX(-10px)'}),
animate('300ms', style({opacity: 1, transform: 'translateX(none)'})),
]),
transition(':leave', [
style({opacity: 1, transform: 'translateX(none)'}),
animate('300ms', style({opacity: 0, transform: 'translateX(-10px)'})),
]),
]),
],
})
export class ReorderComponent {
show = signal(true);
items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];
randomize() {
const randItems = [...this.items];
const newItems = [];
for (let i of this.items) {
const max: number = this.items.length - newItems.length;
const randNum = Math.floor(Math.random() * max);
newItems.push(...randItems.splice(randNum, 1));
}
this.items = newItems;
}
}
@@ -0,0 +1,5 @@
.items {
list-style: none;
padding: 0;
margin: 0;
}
@@ -0,0 +1,9 @@
<!-- #docplaster -->
<h2>Stagger Example</h2>
<ul class="items">
@for(item of items; track item) {
<li class="item">{{ item }}</li>
}
</ul>
@@ -0,0 +1,24 @@
// #docplaster
// #docregion
import {Component, HostBinding, signal} from '@angular/core';
import {trigger, transition, animate, style, query, stagger} from '@angular/animations';
@Component({
selector: 'app-stagger',
templateUrl: 'stagger.component.html',
styleUrls: ['stagger.component.css'],
animations: [
trigger('pageAnimations', [
transition(':enter', [
query('.item', [
style({opacity: 0, transform: 'translateY(-10px)'}),
stagger(200, [animate('500ms ease-in', style({opacity: 1, transform: 'none'}))]),
]),
]),
]),
],
})
export class StaggerComponent {
@HostBinding('@pageAnimations')
items = [1, 2, 3];
}
@@ -13,6 +13,17 @@ export const transitionAnimation = animation([
]);
// #enddocregion animation-const
// #docregion animation-example
export const sharedAnimation = animation([
style({
height: 0,
opacity: 1,
backgroundColor: 'red',
}),
animate('1s'),
]);
// #enddocregion animation-example
// #docregion trigger-const
export const triggerAnimation = trigger('openClose', [
transition('open => closed', [
@@ -0,0 +1,60 @@
/* #docregion animation-shared */
@keyframes sharedAnimation {
to {
height: 0;
opacity: 1;
background-color: 'red';
}
}
.animated-class {
animation: sharedAnimation 1s;
}
/* #enddocregion animation-shared */
/* #docregion animation-states */
.open {
height: '200px';
opacity: 1;
background-color: 'yellow';
transition: all 1s;
}
.closed {
height: '100px';
opacity: 0.8;
background-color: 'blue';
transition: all 1s;
}
/* #enddocregion animation-states */
/* #docregion animation-timing */
.example-element {
animation-duration: 1s;
animation-delay: 500ms;
animation-timing-function: ease-in-out;
}
.example-shorthand {
animation: exampleAnimation 1s ease-in-out 500ms;
}
/* #enddocregion animation-timing */
/* #docregion transition-timing */
.example-element {
transition-duration: 1s;
transition-delay: 500ms;
transition-timing-function: ease-in-out;
transition-property: margin-right;
}
.example-shorthand {
transition: margin-right 1s ease-in-out 500ms;
}
/* #enddocregion transition-timing */
@@ -0,0 +1,27 @@
.container {
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: grid-template-rows 1s;
}
.container.open {
grid-template-rows: 1fr;
}
.container .content {
min-height: 0;
transition: visibility 1s;
padding: 0 20px;
visibility: hidden;
margin-top: 1em;
font-weight: bold;
font-size: 20px;
background-color: blue;
color: #ebebeb;
overflow: hidden;
}
.container.open .content {
visibility: visible;
}
@@ -0,0 +1,10 @@
<!-- #docplaster -->
<h2>Auto Height Example</h2>
<button type="button" (click)="toggle()">Toggle Open/Close</button>
<div class="container" [class.open]="isOpen()">
<div class="content">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
</div>
@@ -0,0 +1,14 @@
// #docplaster
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-auto-height',
templateUrl: 'auto-height.component.html',
styleUrls: ['auto-height.component.css'],
})
export class AutoHeightComponent {
isOpen = signal(true);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
@@ -0,0 +1,69 @@
:host {
display: block;
font-size: 32px;
margin: 20px;
text-align: center;
}
section {
border: 1px solid lightgray;
border-radius: 50px;
}
p {
display: inline-block;
margin: 2rem 0;
text-transform: uppercase;
}
.increment {
animation: increment 300ms;
}
.decrement {
animation: decrement 300ms;
}
.controls {
padding-bottom: 2rem;
}
button {
font: inherit;
border: 0;
background: lightgray;
width: 50px;
border-radius: 10px;
}
button + button {
margin-left: 10px;
}
@keyframes increment {
33% {
color: green;
transform: scale(1.3, 1.2);
}
66% {
color: green;
transform: scale(1.2, 1.2);
}
100% {
transform: scale(1, 1);
}
}
@keyframes decrement {
33% {
color: red;
transform: scale(0.8, 0.9);
}
66% {
color: red;
transform: scale(0.9, 0.9);
}
100% {
transform: scale(1, 1);
}
}
@@ -0,0 +1,8 @@
<h3>Increment and Decrement Example</h3>
<section>
<p #el>Number {{ num() }}</p>
<div class="controls">
<button type="button" (click)="modify(1)">+</button>
<button type="button" (click)="modify(-1)">-</button>
</div>
</section>
@@ -0,0 +1,35 @@
// #docplaster
// #docregion
import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';
@Component({
selector: 'app-increment-decrement',
templateUrl: 'increment-decrement.component.html',
styleUrls: ['increment-decrement.component.css'],
})
export class IncrementDecrementComponent implements OnInit {
num = signal(0);
el = viewChild<ElementRef<HTMLParagraphElement>>('el');
ngOnInit() {
this.el()?.nativeElement.addEventListener('animationend', (ev) => {
if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) {
this.animationFinished();
}
});
}
modify(n: number) {
const targetClass = n > 0 ? 'increment' : 'decrement';
this.num.update((v) => (v += n));
this.el()?.nativeElement.classList.add(targetClass);
}
animationFinished() {
this.el()?.nativeElement.classList.remove('increment', 'decrement');
}
ngOnDestroy() {
this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished);
}
}
@@ -0,0 +1,19 @@
:host {
display: block;
}
.insert-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
opacity: 1;
transition: opacity 1s ease-out, transform 1s ease-out;
@starting-style {
opacity: 0;
transform: translateY(20px);
}
}
@@ -0,0 +1,12 @@
<!-- #docplaster -->
<h2>Insert Element Example</h2>
<nav>
<button type="button" (click)="toggle()">Toggle Element</button>
</nav>
@if (isShown()) {
<div class="insert-container">
<p>The box is inserted</p>
</div>
}
@@ -0,0 +1,15 @@
// #docplaster
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-insert',
templateUrl: 'insert.component.html',
styleUrls: ['insert.component.css'],
})
export class InsertComponent {
isShown = signal(false);
toggle() {
this.isShown.update((isShown) => !isShown);
}
}
@@ -0,0 +1,26 @@
:host {
display: block;
margin-top: 1rem;
}
.open-close-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
font-weight: bold;
font-size: 20px;
height: 100px;
opacity: 0.8;
background-color: blue;
color: #ebebeb;
transition-property: height, opacity, background-color, color;
transition-duration: 1s;
}
.open {
transition-duration: 0.5s;
height: 200px;
opacity: 1;
background-color: yellow;
color: #000000;
}
@@ -0,0 +1,8 @@
<!-- #docplaster -->
<h2>Open / Close Example</h2>
<button type="button" (click)="toggle()">Toggle Open/Close</button>
<div class="open-close-container" [class.open]="isOpen()">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
@@ -0,0 +1,14 @@
// #docplaster
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-open-close',
templateUrl: 'open-close.component.html',
styleUrls: ['open-close.component.css'],
})
export class OpenCloseComponent {
isOpen = signal(true);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
@@ -0,0 +1,24 @@
:host {
display: block;
}
.insert-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
opacity: 1;
transition: opacity 200ms ease-in;
@starting-style {
opacity: 0;
}
}
.deleting {
opacity: 0;
transform: translateY(20px);
transition: opacity 500ms ease-out, transform 500ms ease-out;
}
@@ -0,0 +1,13 @@
<!-- #docplaster -->
<h2>Remove Element Example</h2>
<nav>
<button type="button" (click)="toggle()">Toggle Element</button>
</nav>
@if (isShown()) {
<div class="insert-container" [class.deleting]="deleting()">
<p>The box is inserted</p>
</div>
}
@@ -0,0 +1,28 @@
// #docplaster
import {Component, ElementRef, inject, signal} from '@angular/core';
@Component({
selector: 'app-remove',
templateUrl: 'remove.component.html',
styleUrls: ['remove.component.css'],
})
export class RemoveComponent {
isShown = signal(false);
deleting = signal(false);
private el = inject(ElementRef);
toggle() {
if (this.isShown()) {
const target = this.el.nativeElement.querySelector('.insert-container');
target.addEventListener('transitionend', () => this.hide());
this.deleting.set(true);
} else {
this.isShown.update((isShown) => !isShown);
}
}
hide() {
this.isShown.set(false);
this.deleting.set(false);
}
}
@@ -0,0 +1,15 @@
.items {
list-style: none;
padding: 0;
margin: 0;
}
.items .item {
transition-property: opacity, transform;
transition-duration: 500ms;
@starting-style {
opacity: 0;
transform: translateX(-10px);
}
}
@@ -0,0 +1,9 @@
<!-- #docplaster -->
<h1>Reordering List Example</h1>
<button type="button" (click)="randomize()">Randomize</button>
<ul class="items">
@for(item of items; track item) {
<li class="item">{{ item }}</li>
}
</ul>
@@ -0,0 +1,24 @@
// #docplaster
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-reorder',
templateUrl: './reorder.component.html',
styleUrls: ['reorder.component.css'],
})
export class ReorderComponent {
show = signal(true);
items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];
randomize() {
const randItems = [...this.items];
const newItems = [];
for (let i of this.items) {
const max: number = this.items.length - newItems.length;
const randNum = Math.floor(Math.random() * max);
newItems.push(...randItems.splice(randNum, 1));
}
this.items = newItems;
}
}
@@ -0,0 +1,16 @@
.items {
list-style: none;
padding: 0;
margin: 0;
}
.items .item {
transition-property: opacity, transform;
transition-duration: 500ms;
transition-delay: calc(200ms * var(--index));
@starting-style {
opacity: 0;
transform: translateX(-10px);
}
}
@@ -0,0 +1,10 @@
<!-- #docplaster -->
<h1>Stagger Example</h1>
<button type="button" (click)="refresh()">Refresh</button>
@if (show()) {
<ul class="items">
@for(item of items; track item) {
<li class="item" style="--index: {{ item }}">{{item}}</li>
}
</ul>
}
@@ -0,0 +1,19 @@
// #docplaster
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-stagger',
templateUrl: './stagger.component.html',
styleUrls: ['stagger.component.css'],
})
export class StaggerComponent {
show = signal(true);
items = [1, 2, 3];
refresh() {
this.show.set(false);
setTimeout(() => {
this.show.set(true);
}, 10);
}
}
@@ -1,5 +1,7 @@
# Complex animation sequences
IMPORTANT: The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](guide/animations/migration#complex-sequences) to learn how you can start using pure CSS animations in your apps.
So far, we've learned simple animations of single HTML elements.
Angular also lets you animate coordinated sequences, such as an entire grid or list of elements as they enter and leave a page.
You can choose to run multiple animations in parallel, or run discrete animations sequentially, one following another.
+178
View File
@@ -0,0 +1,178 @@
# Animating your Application with CSS
CSS offers a robust set of tools for you to create beautiful and engaging animations within your application.
## How to write animations in native CSS
If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here's a few of them:
[MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations)
[W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp)
[The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/)
[CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)
and a couple of videos:
[Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw)
[Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5)
Check some of these various guides and tutorials out, and then come back to this guide.
## Creating Reusable Animations
You can create reusable animations that can be shared across your application using `@keyframes`. Define keyframe animations in a shared CSS file, and you'll be able to re-use those keyframe animations wherever you want within your application.
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-shared"/>
Adding the class `animated-class` to an element would trigger the animation on that element.
## Animating a Transition
### Animating State and Styles
You may want to animate between two different states, for example when an element is opened or closed. You can accomplish this by using CSS classes either using a keyframe animation or transition styling.
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-states"/>
Triggering the `open` or `closed` state is done by toggling classes on the element in your component. You can find examples of how to do this in our [template guide](guide/templates/binding#css-class-and-style-property-bindings).
You can see similar examples in the template guide for [animating styles directly](guide/templates/binding#css-style-properties).
### Transitions, Timing, and Easing
Animating often requires adjusting timing, delays and easeing behaviors. This can be done using several css properties or shorthand properties.
Specify `animation-duration`, `animation-delay`, and `animation-timing-function` for a keyframe animation in CSS, or alternatively use the `animation` shorthand property.
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-timing"/>
Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`.
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="transition-timing"/>
### Triggering an Animation
Animations can be triggered by toggling CSS styles or classes. Once a class is present on an element, the animation will occur. Removing the class will revert the element back to whatever CSS is defined for that element. Here's an example:
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/open-close.component.ts">
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.ts" />
<docs-code header="src/app/open-close.component.html" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.html" />
<docs-code header="src/app/open-close.component.css" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.css"/>
</docs-code-multifile>
## Transition and Triggers
### Animating Auto Height
You can use css-grid to animate to auto height.
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.ts">
<docs-code header="src/app/auto-height.component.ts" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.ts" />
<docs-code header="src/app/auto-height.component.html" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.html" />
<docs-code header="src/app/auto-height.component.css" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.css" />
</docs-code-multifile>
If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/] for more information.
### Animate entering and leaving a view
You can create animations for when an item enters a view or leaves a view. Let's start by looking at how to animate an element leaving a view.
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/insert.component.ts">
<docs-code header="src/app/insert.component.ts" path="adev/src/content/examples/animations/src/app/native-css/insert.component.ts" />
<docs-code header="src/app/insert.component.html" path="adev/src/content/examples/animations/src/app/native-css/insert.component.html" />
<docs-code header="src/app/insert.component.css" path="adev/src/content/examples/animations/src/app/native-css/insert.component.css" />
</docs-code-multifile>
Leaving a view is slightly more complex. The element removal needs to be delayed until the exit animation is complete. This requires a bit of extra code in your component class to accomplish.
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/remove.component.ts">
<docs-code header="src/app/remove.component.ts" path="adev/src/content/examples/animations/src/app/native-css/remove.component.ts" />
<docs-code header="src/app/remove.component.html" path="adev/src/content/examples/animations/src/app/native-css/remove.component.html" />
<docs-code header="src/app/remove.component.css" path="adev/src/content/examples/animations/src/app/native-css/remove.component.css" />
</docs-code-multifile>
### Animating increment and decrement
Animating on increment and decrement is a common pattern in applications. Here's an example of how you can accomplish that behavior.
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.ts">
<docs-code header="src/app/increment-decrement.component.ts" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.ts" />
<docs-code header="src/app/increment-decrement.component.html" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.html" />
<docs-code header="src/app/increment-decrement.component.css" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.css" />
</docs-code-multifile>
### Disabling an animation or all animations
If you'd like to disable the animations that you've specified, you have multiple options.
1. Create a custom class that forces animation and transition to `none`.
```css
.no-animation {
animation: none !important;
transition: none !important;
}
```
Applying this class to an element prevents any animation from firing on that element. You could alternatively scope this to your entire DOM or section of your DOM to enforce this behavior. However, this prevents animation events from firing. If you are awaiting animation events for element removal, this solution won't work. A workaround is to set durations to 1 millisecond instead.
2. Use the [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query to ensure no animations play for users that prefer less animation.
3. Prevent adding animation classes programatically
### Animation Callbacks
If you have actions you would like to execute at certain points during animations, there are a number of available events you can listen to. Here's a few of them.
[`OnAnimationStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event)
[`OnAnimationEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event)
[`OnAnimationIteration`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationitration_event)
[`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event)
[`OnTransitionStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionstart_event)
[`OnTransitionRun`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionrun_event)
[`OnTransitionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event)
[`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event)
The Web Animations API has a lot of additional functionality. [Take a look at the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) to see all the available animation APIs.
NOTE: Be aware of bubbling issues with these callbacks. If you are animating children and parents, the events bubble up from children to parents. Consider stopping propagation or looking at more details within the event to determine if you're responding to the desired event target rather than an event bubbling up from a child node. You can examine the `animationname` property or the properties being transitioned to verify you have the right nodes.
## Complex Sequences
Animations are often more complicated than just a simple fade in or fade out. You may have lots of complicated sequences of animations you may want to run. Let's take a look at some of those possible scenarios.
### Staggering animations in a list
One common effect is to stagger the animations of each item in a list to create a cascade effect. This can be accomplished by utilizing `animation-delay` or `transition-delay`. Here is an example of what that CSS might look like.
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/stagger.component.ts">
<docs-code header="src/app/stagger.component.ts" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.ts" />
<docs-code header="src/app/stagger.component.html" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.html" />
<docs-code header="src/app/stagger.component.css" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.css" />
</docs-code-multifile>
### Parallel Animations
You can apply multiple animations to an element at once using the `animation` shorthand property. Each can have their own durations and delays. This allows you to compose animations together and create complicated effects.
```css
.target-element {
animation: rotate 3s, fade-in 2s;
}
```
In this example, the `rotate` and `fade-in` animations fire at the same time, but have different durations.
### Animating the items of a reordering list
Items in a `@for` loop will be removed and re-added, which will fire off animations using `@starting-styles` for entry animations. Removal animations will require additional code to add the event listener, as seen in the example above.
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/reorder.component.ts">
<docs-code header="src/app/reorder.component.ts" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.ts" />
<docs-code header="src/app/reorder.component.html" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.html" />
<docs-code header="src/app/reorder.component.css" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.css" />
</docs-code-multifile>
## Programmatic control of animations
You can retrieve animations off an element directly using [`Element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). This returns an array of every [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) on that element. You can use the `Animation` API to do much more than you could with what the `AnimationPlayer` from the animations package offered. From here you can `cancel()`, `play()`, `pause()`, `reverse()` and much more. This native API should provide everything you need to control your animations.
@@ -0,0 +1,264 @@
# Migrating away from Angular's Animations package
Almost all the features supported by `@angular/animations` have simpler alternatives with native CSS. Consider removing the Angular Animations package from your application, as the package can contribute around 60 kilobytes to your JavaScript bundle. Native CSS animations offer superior performance, as they can benefit from hardware acceleration. Animations defined in the animations package lack that ability. This guide walks through the process of refactoring your code from `@angular/animations` to native CSS animations.
## How to write animations in native CSS
If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here's a few of them:
[MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations)
[W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp)
[The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/)
[CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)
and a couple of videos:
[Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw)
[Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5)
Check some of these various guides and tutorials out, and then come back to this guide.
## Creating Reusable Animations
Just like with the animations package, you can create reusable animations that can be shared across your application. The animations package version of this had you using the `animation()` function in a shared typescript file. The native CSS version of this is similar, but lives in a shared CSS file.
#### With Animations Package
<docs-code header="src/app/animations.ts" path="adev/src/content/examples/animations/src/app/animations.1.ts" visibleRegion="animation-example"/>
#### With Native CSS
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-shared"/>
Adding the class `animated-class` to an element would trigger the animation on that element.
## Animating a Transition
### Animating State and Styles
The animations package allowed you to define various states using the [`state()`](api/animations/state) function within a component. Examples might be an `open` or `closed` state containing the styles for each respective state within the definition. For example:
#### With Animations Package
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/open-close.component.ts" visibleRegion="state1"/>
This same behavior can be accomplished natively by using CSS classes either using a keyframe animation or transition styling.
#### With Native CSS
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-states"/>
Triggering the `open` or `closed` state is done by toggling classes on the element in your component. You can find examples of how to do this in our [template guide](guide/templates/binding#css-class-and-style-property-bindings).
You can see similar examples in the template guide for [animating styles directly](guide/templates/binding#css-style-properties).
### Transitions, Timing, and Easing
The animations package `animate()` function allows for providing timing, like duration, delays and easing. This can be done natively with CSS using several css properties or shorthand properties.
Specify `animation-duration`, `animation-delay`, and `animation-timing-function` for a keyframe animation in CSS, or alternatively use the `animation` shorthand property.
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-timing"/>
Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`.
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="transition-timing"/>
### Triggering an Animation
The animations package required specifying triggers using the `trigger()` function and nesting all of your states within it. With native CSS, this is unnecessary. Animations can be triggered by toggling CSS styles or classes. Once a class is present on an element, the animation will occur. Removing the class will revert the element back to whatever CSS is defined for that element. This results in significantly less code to do the same animation. Here's an example:
#### With Animations Package
<docs-code-multifile>
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/open-close.component.ts" />
<docs-code header="src/app/open-close.component.html" path="adev/src/content/examples/animations/src/app/animations-package/open-close.component.html" />
<docs-code header="src/app/open-close.component.css" path="adev/src/content/examples/animations/src/app/animations-package/open-close.component.css"/>
</docs-code-multifile>
#### With Native CSS
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/open-close.component.ts">
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.ts" />
<docs-code header="src/app/open-close.component.html" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.html" />
<docs-code header="src/app/open-close.component.css" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.css"/>
</docs-code-multifile>
## Transition and Triggers
### Predefined State and wildcard matching
The animations package offers the ability to match your defined states to a transition via strings. For example, animating from open to closed would be `open => closed`. You can use wildcards to match any state to a target state, like `* => closed` and the `void` keyword can be used for entering and exiting states. For example: `* => void` for when an element leaves a view or `void => *` for when the element enters a view.
These state matching patterns are not needed at all when animating with CSS directly. You can manage what transitions and `@keyframes` animations apply based on whatever classes you set and / or styles you set on the elements. You can also add `@starting-style` to control how the element looks upon immediately entering the DOM.
### Automatic Property Calculation with Wildcards
The animations package offers the ability to animate things that have been historically difficult to animate, like animating a set height to `height: auto`. You can now do this with pure CSS as well.
#### With Animations Package
<docs-code-multifile>
<docs-code header="src/app/auto-height.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/auto-height.component.ts" />
<docs-code header="src/app/auto-height.component.html" path="adev/src/content/examples/animations/src/app/animations-package/auto-height.component.html" />
<docs-code header="src/app/auto-height.component.css" path="adev/src/content/examples/animations/src/app/animations-package/auto-height.component.css" />
</docs-code-multifile>
You can use css-grid to animate to auto height.
#### With Native CSS
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.ts">
<docs-code header="src/app/auto-height.component.ts" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.ts" />
<docs-code header="src/app/auto-height.component.html" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.html" />
<docs-code header="src/app/auto-height.component.css" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.css" />
</docs-code-multifile>
If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/] for more information.
### Animate entering and leaving a view
The animations package offered the previously mentioned pattern matching for entering and leaving but also included the shorthand aliases of `:enter` and `:leave`.
#### With Animations Package
<docs-code-multifile>
<docs-code header="src/app/insert-remove.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/insert-remove.component.ts" />
<docs-code header="src/app/insert-remove.component.html" path="adev/src/content/examples/animations/src/app/animations-package/insert-remove.component.html" />
<docs-code header="src/app/insert-remove.component.css" path="adev/src/content/examples/animations/src/app/animations-package/insert-remove.component.css" />
</docs-code-multifile>
Here's how the same thing can be accomplished without the animations package.
#### With Native CSS
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/insert.component.ts">
<docs-code header="src/app/insert.component.ts" path="adev/src/content/examples/animations/src/app/native-css/insert.component.ts" />
<docs-code header="src/app/insert.component.html" path="adev/src/content/examples/animations/src/app/native-css/insert.component.html" />
<docs-code header="src/app/insert.component.css" path="adev/src/content/examples/animations/src/app/native-css/insert.component.css" />
</docs-code-multifile>
Leaving a view is slightly more complex. The element removal needs to be delayed until the exit animation is complete. This requires a bit of extra code in your component class to accomplish.
#### With Native CSS
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/remove.component.ts">
<docs-code header="src/app/remove.component.ts" path="adev/src/content/examples/animations/src/app/native-css/remove.component.ts" />
<docs-code header="src/app/remove.component.html" path="adev/src/content/examples/animations/src/app/native-css/remove.component.html" />
<docs-code header="src/app/remove.component.css" path="adev/src/content/examples/animations/src/app/native-css/remove.component.css" />
</docs-code-multifile>
### Animating increment and decrement
Along with the aforementioned `:enter` and `:leave`, there's also `:increment` and `:decrement`. You can animate these also by adding and removing classes. Unlike the animation package built-in aliases, there is no automatic application of classes when the values go up or down. You can apply the appropriate classes programmatically. Here's an example:
#### With Animations Package
<docs-code-multifile>
<docs-code header="src/app/increment-decrement.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/increment-decrement.component.ts" />
<docs-code header="src/app/increment-decrement.component.html" path="adev/src/content/examples/animations/src/app/animations-package/increment-decrement.component.html" />
<docs-code header="src/app/increment-decrement.component.css" path="adev/src/content/examples/animations/src/app/animations-package/increment-decrement.component.css" />
</docs-code-multifile>
#### With Native CSS
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.ts">
<docs-code header="src/app/increment-decrement.component.ts" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.ts" />
<docs-code header="src/app/increment-decrement.component.html" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.html" />
<docs-code header="src/app/increment-decrement.component.css" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.css" />
</docs-code-multifile>
### Parent / Child Animations
Unlike the animations package, when multiple animations are specified within a given component, no animation has priority over another and nothing blocks any animation from firing. Any sequencing of animations would have to be handled by your definition of your CSS animation, using animation / transition delay, and / or using `animationend` or `transitionend` to handle adding the next css to be animated.
### Disabling an animation or all animations
With native CSS animations, if you'd like to disable the animations that you've specified, you have multiple options.
1. Create a custom class that forces animation and transition to `none`.
```css
.no-animation {
animation: none !important;
transition: none !important;
}
```
Applying this class to an element prevents any animation from firing on that element. You could alternatively scope this to your entire DOM or section of your DOM to enforce this behavior. However, this prevents animation events from firing. If you are awaiting animation events for element removal, this solution won't work. A workaround is to set durations to 1 millisecond instead.
2. Use the [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query to ensure no animations play for users that prefer less animation.
3. Prevent adding animation classes programatically
### Animation Callbacks
The animations package exposed callbacks for you to use in the case that you want to do something when the animation has finished. Native CSS animations also have these callbacks.
[`OnAnimationStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event)
[`OnAnimationEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event)
[`OnAnimationIteration`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationitration_event)
[`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event)
[`OnTransitionStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionstart_event)
[`OnTransitionRun`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionrun_event)
[`OnTransitionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event)
[`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event)
The Web Animations API has a lot of additional functionality. [Take a look at the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) to see all the available animation APIs.
NOTE: Be aware of bubbling issues with these callbacks. If you are animating children and parents, the events bubble up from children to parents. Consider stopping propagation or looking at more details within the event to determine if you're responding to the desired event target rather than an event bubbling up from a child node. You can examine the `animationname` property or the properties being transitioned to verify you have the right nodes.
## Complex Sequences
The animations package has built-in functionality for creating complex sequences. These sequences are all entirely possible without the animations package.
### Targeting specific elements
In the animations package, you could target specific elements by using the `query()` function to find specific elements by a CSS class name, similar to [`document.querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). This is unnecessary in a native CSS animation world. Instead, you can use your CSS selectors to target sub-classes and apply any desired `transform` or `animation`.
To toggle classes for child nodes within a template, you can use class and style bindings to add the animations at the right points.
### Stagger()
The `stagger()` function allowed you to delay the animation of each item in a list of items by a specified time to create a cascade effect. You can replicate this behavior in native CSS by utilizing `animation-delay` or `transition-delay`. Here is an example of what that CSS might look like.
#### With Animations Package
<docs-code-multifile>
<docs-code header="src/app/stagger.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/stagger.component.ts" />
<docs-code header="src/app/stagger.component.html" path="adev/src/content/examples/animations/src/app/animations-package/stagger.component.html" />
<docs-code header="src/app/stagger.component.css" path="adev/src/content/examples/animations/src/app/animations-package/stagger.component.css" />
</docs-code-multifile>
#### With Native CSS
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/stagger.component.ts">
<docs-code header="src/app/stagger.component.ts" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.ts" />
<docs-code header="src/app/stagger.component.html" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.html" />
<docs-code header="src/app/stagger.component.css" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.css" />
</docs-code-multifile>
### Parallel Animations
The animations package has a `group()` function to play multiple animations at the same time. In CSS, you have full control over animation timing. If you have multiple animations defined, you can apply all of them at once.
```css
.target-element {
animation: rotate 3s, fade-in 2s;
}
```
In this example, the `rotate` and `fade-in` animations fire at the same time.
### Animating the items of a reordering list
Items reordering in a list works out of the box using the previously described techniques. No additional special work is required. Items in a `@for` loop will be removed and re-added properly, which will fire off animations using `@starting-styles` for entry animations. Removal animations will require additional code to add the event listener, as seen in the example above.
#### With Animations Package<
<docs-code-multifile>
<docs-code header="src/app/reorder.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/reorder.component.ts" />
<docs-code header="src/app/reorder.component.html" path="adev/src/content/examples/animations/src/app/animations-package/reorder.component.html" />
<docs-code header="src/app/reorder.component.css" path="adev/src/content/examples/animations/src/app/animations-package/reorder.component.css" />
</docs-code-multifile>
#### With Native CSS
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/reorder.component.ts">
<docs-code header="src/app/reorder.component.ts" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.ts" />
<docs-code header="src/app/reorder.component.html" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.html" />
<docs-code header="src/app/reorder.component.css" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.css" />
</docs-code-multifile>
## Migrating usages of AnimationPlayer
The `AnimationPlayer` class allows access to an animation to do more advanced things like pause, play, restart, and finish an animation through code. All of these things can be handled natively as well.
You can retrieve animations off an element directly using [`Element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). This returns an array of every [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) on that element. You can use the `Animation` API to do much more than you could with what the `AnimationPlayer` from the animations package offered. From here you can `cancel()`, `play()`, `pause()`, `reverse()` and much more. This native API should provide everything you need to control your animations.
## Route Transitions
You can use view transitions to animate between routes. See the [Route Transition Animations Guide](guide/animations/route-animations) to get started.
@@ -1,5 +1,7 @@
# Introduction to Angular animations
IMPORTANT: The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start using pure CSS animations in your apps.
Animation provides the illusion of motion: HTML elements change styling over time.
Well-designed animations can make your application more fun and straightforward to use, but they aren't just cosmetic.
Animations can improve your application and user experience in a number of ways:
@@ -1,5 +1,7 @@
# Reusable animations
IMPORTANT: The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](guide/animations/migration#creating-reusable-animations) to learn how you can start using pure CSS animations in your apps.
This topic provides some examples of how to create reusable animations.
## Create reusable animations
@@ -1,5 +1,7 @@
# Animation transitions and triggers
IMPORTANT: The Angular team recommends using native CSS for animations instead of the Animations package for all new code. Use this guide to understand existing code built with the Animations Package. See [Migrating away from Angular's Animations package](guide/animations/migration#transition-and-triggers) to learn how you can start using pure CSS animations in your apps.
This guide goes into depth on special transition states such as the `*` wildcard and `void`. It shows how these special states are used for elements entering and leaving a view.
This section also explores multiple animation triggers, animation callbacks, and sequence-based animation using keyframes.