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
W3Schools CSS3 Animations guide
The Complete CSS Animations Tutorial
CSS Animation for Beginners
and a couple of videos:
Learn CSS Animation in 9 Minutes
Net Ninja CSS Animation Tutorial Playlist
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
src/app/animations.ts
import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';export const transitionAnimation = animation([ style({ height: '{{ height }}', opacity: '{{ opacity }}', backgroundColor: '{{ backgroundColor }}', }), animate('{{ time }}'),]);export const sharedAnimation = animation([ style({ height: 0, opacity: 1, backgroundColor: 'red', }), animate('1s'),]);export const triggerAnimation = trigger('openClose', [ transition('open => closed', [ useAnimation(transitionAnimation, { params: { height: 0, opacity: 1, backgroundColor: 'red', time: '1s', }, }), ]),]);
With Native CSS
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.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;}
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()
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
src/app/open-close.component.ts
import {Component, Input} from '@angular/core';import {trigger, transition, state, animate, style, AnimationEvent} 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.8, backgroundColor: 'blue', }), ), transition('open => closed', [animate('1s')]), transition('closed => open', [animate('0.5s')]), transition('* => closed', [animate('1s')]), transition('* => open', [animate('0.5s')]), transition('open <=> closed', [animate('0.5s')]), transition('* => open', [animate('1s', style({opacity: '*'}))]), transition('* => *', [animate('1s')]), ]), ], templateUrl: 'open-close.component.html', styleUrls: ['open-close.component.css'],})export class OpenCloseComponent { @Input() logging = false; isOpen = true; toggle() { this.isOpen = !this.isOpen; } onAnimationEvent(event: AnimationEvent) { if (!this.logging) { return; } // openClose is trigger name in this example console.warn(`Animation Trigger: ${event.triggerName}`); // phaseName is "start" or "done" console.warn(`Phase: ${event.phaseName}`); // in our example, totalTime is 1000 (number of milliseconds in a second) console.warn(`Total time: ${event.totalTime}`); // in our example, fromState is either "open" or "closed" console.warn(`From: ${event.fromState}`); // in our example, toState either "open" or "closed" console.warn(`To: ${event.toState}`); // the HTML element itself, the button in this case console.warn(`Element: ${event.element}`); }}
This same behavior can be accomplished natively by using CSS classes either using a keyframe animation or transition styling.
With Native CSS
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.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;}
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.
You can see similar examples in the template guide for animating styles directly.
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.
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.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;}
Similarly, you can use transition-duration
, transition-delay
, and transition-timing-function
and the transition
shorthand for animations that are not using @keyframes
.
src/app/animations.css
@keyframes sharedAnimation { to { height: 0; opacity: 1; background-color: 'red'; }}.animated-class { animation: sharedAnimation 1s;}.open { height: '200px'; opacity: 1; background-color: 'yellow'; transition: all 1s;}.closed { height: '100px'; opacity: 0.8; background-color: 'blue'; transition: all 1s;}.example-element { animation-duration: 1s; animation-delay: 500ms; animation-timing-function: ease-in-out;}.example-shorthand { animation: exampleAnimation 1s ease-in-out 500ms;}.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;}
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
src/app/open-close.component.ts
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); }}
src/app/open-close.component.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>
src/app/open-close.component.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;}
With Native CSS
src/app/open-close.component.ts
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); }}
src/app/open-close.component.html
<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>
src/app/open-close.component.css
: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;}
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
src/app/auto-height.component.ts
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); }}
src/app/auto-height.component.html
<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>
src/app/auto-height.component.css
.container { display: block; overflow: hidden;}.container .content { padding: 20px; margin-top: 1em; font-weight: bold; font-size: 20px; background-color: blue; color: #ebebeb;}
You can use css-grid to animate to auto height.
With Native CSS
src/app/auto-height.component.ts
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); }}
src/app/auto-height.component.html
<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>
src/app/auto-height.component.css
.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;}
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 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
src/app/insert-remove.component.ts
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; }}
src/app/insert-remove.component.html
<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>}
src/app/insert-remove.component.css
: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;}
Here's how the same thing can be accomplished without the animations package.
With Native CSS
src/app/insert.component.ts
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); }}
src/app/insert.component.html
<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>}
src/app/insert.component.css
: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); }}
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
src/app/remove.component.ts
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); }}
src/app/remove.component.html
<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>}
src/app/remove.component.css
: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;}
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
src/app/increment-decrement.component.ts
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)); }}
src/app/increment-decrement.component.html
<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>
src/app/increment-decrement.component.css
: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;}
With Native CSS
src/app/increment-decrement.component.ts
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); }}
src/app/increment-decrement.component.html
<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>
src/app/increment-decrement.component.css
: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); }}
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.
- Create a custom class that forces animation and transition to
none
.
.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.
Use the
prefers-reduced-motion
media query to ensure no animations play for users that prefer less animation.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
OnAnimationEnd
OnAnimationIteration
OnAnimationCancel
OnTransitionStart
OnTransitionRun
OnTransitionEnd
OnTransitionCancel
The Web Animations API has a lot of additional functionality. Take a look at the documentation 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()
. 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
src/app/stagger.component.ts
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];}
src/app/stagger.component.html
<h2>Stagger Example</h2><ul class="items"> @for(item of items; track item) { <li class="item">{{ item }}</li> }</ul>
src/app/stagger.component.css
.items { list-style: none; padding: 0; margin: 0;}
With Native CSS
src/app/stagger.component.ts
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); }}
src/app/stagger.component.html
<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>}
src/app/stagger.component.css
.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); }}
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.
.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<
src/app/reorder.component.ts
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; }}
src/app/reorder.component.html
<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>
src/app/reorder.component.css
.items { list-style: none; padding: 0; margin: 0;}
With Native CSS
src/app/reorder.component.ts
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; }}
src/app/reorder.component.html
<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>
src/app/reorder.component.css
.items { list-style: none; padding: 0; margin: 0;}.items .item { transition-property: opacity, transform; transition-duration: 500ms; @starting-style { opacity: 0; transform: translateX(-10px); }}
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()
. This returns an array of every 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 to get started.