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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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;

}
Enter fullscreen mode Exit fullscreen mode

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)
}

Enter fullscreen mode Exit fullscreen mode

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)
      });
  }  
}

Enter fullscreen mode Exit fullscreen mode

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)
      });
  }  
}

Enter fullscreen mode Exit fullscreen mode

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)
      });
  }  
}

Enter fullscreen mode Exit fullscreen mode

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.