Following recent framework updates, Angular now advises developers to phase out the @angular/animations package, favoring lighter CSS or JavaScript approaches instead. While pure CSS handles plenty of everyday animation needs, JavaScript steps in when things get trickier. For those more demanding scenarios, external tools—like the CSS-powered Animate.css, or JavaScript-driven Anime.js, GSAP, and Popmotion—offer powerful possibilities.
Here, we’ll demonstrate how to bring Anime.js, a swift and flexible JavaScript animation library, into your Angular project and put it to work.
npm i --save animejs
Setup Element to Animate
Before you can animate a template element, assign it an identifier (like #box in the example) so your TypeScript code can reference it. Begin by constructing a styled box with these initial styles:
<!-- app.component.html -->
<div class="animation-demo-container">
<div class="card-container">
<div #box class="demo-card"></div>
</div>
</div>
Apply the following CSS rules:
// app.component.scss
.animation-demo-container {
display: flex;
flex-direction: column;
border-radius: 0.5rem;
border-width: 1px;
border-color: rgb(14 116 144);
background-color: rgb(30 41 59);
min-height: 350px;
.card-container {
flex-grow: 1;
padding: 1.25rem;
.demo-card {
height: 10rem;
width: 10rem;
border-radius: 0.5rem;
border-width: 1px;
background-color: rgb(125 211 252);
}
}
}
Setup Animation Controls
Three buttons need to be placed in the template: one for running a plain animation, one for a bouncing effect, and a third that toggles between playing and pausing both animations:
<!-- app.component.html -->
<div class="animation-demo-container">
<div class="card-container">
<div #box class="demo-card"></div>
</div>
<!-- add the following 👇 -->
<div class="demo-controller-container">
<button class="demo-button" (click)="animateDefaultCard()" [disabled]="isAnimating()">Default</button>
<button class="demo-button" (click)="animateSpringCard()" [disabled]="isAnimating()">Bounce</button>
@if (isAnimating()) {
<button class="demo-button" (click)="playPause()">{{
isPaused() ? 'Play' : 'Pause'
}}</button>
}
</div>
</div>
Now give the buttons some visual flair:
// app.component.scss
.animation-demo-container {
display: flex;
flex-direction: column;
border-radius: 0.5rem;
border-width: 1px;
border-color: rgb(14 116 144);
background-color: rgb(30 41 59);
min-height: 350px;
.card-container {
flex-grow: 1;
padding: 1.25rem;
.demo-card {
height: 10rem;
width: 10rem;
border-radius: 0.5rem;
border-width: 1px;
background-color: rgb(125 211 252);
}
}
// add the following 👇
.demo-controller-container {
display: flex;
flex-direction: row;
gap: 0.75rem;
border-radius: 0 0.5rem 0.5rem 0;
background-color: rgb(14 116 144);
padding: 1rem 1.25rem;
.demo-button {
border-radius: 0.25rem;
background-color: rgb(255 255 255);
padding: 0.5rem 0.75rem;
font-weight: 500;
}
}
}
Add AnimeJS Animation
First, grab the target element with the ViewChild decorator:
// app.component.ts
// Update this 👇
import { Component, ElementRef, ViewChild } from '@angular/core';
@Component({
standalone: true,
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
// Add this 👇
@ViewChild('box') box!: ElementRef;
}
Afterwards, declare a pair of signal variables—isAnimating and isPaused—for monitoring the animation’s status.
// app.component.ts
// Update this 👇
import { Component, ElementRef, signal, ViewChild } from '@angular/core';
@Component({
standalone: true,
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
@ViewChild('box') box!: ElementRef;
// Add this 👇
isAnimating = signal<boolean>(false)
isPaused = signal<boolean>(false)
}
Define a _animateCard method that kicks off the AnimeJS animation. This method takes an easing argument to determine how the animation's easing behavior is configured.
// app.component.ts
import { Component, ElementRef, signal, ViewChild } from '@angular/core';
// Add this 👇
import { animate, EasingParam, JSAnimation } from 'animejs';
@Component({
standalone: true,
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
@ViewChild('box') box!: ElementRef;
isAnimating = signal<boolean>(false)
isPaused = signal<boolean>(false)
// Add this 👇
private _animeAnimation: JSAnimation | undefined = undefined
// Add this 👇
private _animateCard(ease: EasingParam): void {
if (this.isAnimating()) {
return
}
this.isAnimating.set(true)
this.isPaused.set(false)
this._animeAnimation = animate(this.box.nativeElement, {
x: 250,
duration: 400,
ease: 'out'
})
this._animeAnimation
.then(() => {
this._animeAnimation = animate(
this.box.nativeElement, {
x: 0,
duration: 500,
ease,
}
)
this._animeAnimation
.then(() => {
this.isAnimating.set(false)
})
.catch(() => {
this.isAnimating.set(false)
});
})
.catch(() => {
this.box.nativeElement.x = 0;
this.isAnimating.set(false)
});
}
}
Define a toggle function for the animation that reads its current state. Based on whether it's already running, the function updates the isAnimating and isPaused flags accordingly: if active, it enables pausing; if idle, it kicks off the sequence.
// app.component.ts
import { Component, ElementRef, signal, ViewChild } from '@angular/core';
import { animate, EasingParam, JSAnimation } from 'animejs';
@Component({
standalone: true,
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
@ViewChild('box') box!: ElementRef;
isAnimating = signal<boolean>(false)
isPaused = signal<boolean>(false)
private _animeAnimation: JSAnimation | undefined = undefined
// Add this 👇
playPause(): void {
if (!this._animeAnimation) {
return;
}
if (this.isAnimating()) {
if (this.isPaused()) {
this._animeAnimation.resume()
this.isPaused.set(false)
} else {
this._animeAnimation.pause()
this.isPaused.set(true)
}
}
}
private _animateCard(ease: EasingParam): void {
if (this.isAnimating()) {
return
}
this.isAnimating.set(true)
this.isPaused.set(false)
this._animeAnimation = animate(this.box.nativeElement, {
x: 250,
duration: 400,
ease: 'out'
})
this._animeAnimation
.then(() => {
this._animeAnimation = animate(
this.box.nativeElement, {
x: 0,
duration: 500,
ease,
}
)
this._animeAnimation
.then(() => {
this.isAnimating.set(false)
})
.catch(() => {
this.isAnimating.set(false)
});
})
.catch(() => {
this.box.nativeElement.x = 0;
this.isAnimating.set(false)
});
}
}
To wrap things up, define animateDefaultCard and animateSpringCard as the two functions that trigger _animateCard, each passing in its own distinct easing option.
// app.component.ts
import { Component, ElementRef, signal, ViewChild } from '@angular/core';
import { animate, EasingParam, JSAnimation } from 'animejs';
@Component({
standalone: true,
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
@ViewChild('box') box!: ElementRef;
isAnimating = signal<boolean>(false)
isPaused = signal<boolean>(false)
private _animeAnimation: JSAnimation | undefined = undefined
// Add this 👇
animateDefaultCard(): void {
this._animateCard('easeInOut');
}
// Add this 👇
animateSpringCard(): void {
this._animateCard('outBounce');
}
playPause(): void {
if (!this._animeAnimation) {
return;
}
if (this.isAnimating()) {
if (this.isPaused()) {
this._animeAnimation.resume()
this.isPaused.set(false)
} else {
this._animeAnimation.pause()
this.isPaused.set(true)
}
}
}
private _animateCard(ease: EasingParam): void {
if (this.isAnimating()) {
return
}
this.isAnimating.set(true)
this.isPaused.set(false)
this._animeAnimation = animate(this.box.nativeElement, {
x: 250,
duration: 400,
ease: 'out'
})
this._animeAnimation
.then(() => {
this._animeAnimation = animate(
this.box.nativeElement, {
x: 0,
duration: 500,
ease,
}
)
this._animeAnimation
.then(() => {
this.isAnimating.set(false)
})
.catch(() => {
this.isAnimating.set(false)
});
})
.catch(() => {
this.box.nativeElement.x = 0;
this.isAnimating.set(false)
});
}
}
Conclusion
Compared to @angular/animations, this strategy hands you tighter reins over animation properties, durations, and easing curves. What we've seen here only scratches the surface. With this library's flexibility, you can orchestrate sophisticated multi-step animations with ease. Play around with alternative easing options and property combinations to make your interface feel truly dynamic.
Go ahead and experiment! The live demo, plus additional library examples, lives in the Angular Animation Explorer.
