Home

Ways to use Anime.js with Svelte

Published 31 July 2025

Table of Contents

Anime.js

Anime.js released it’s v4 recently and I gave it a shot for the first time. The API has clicked with me and it’s clearly capable of making some incredible animations if the website is any indication.

Integrating with Svelte

Anime.js doesn’t have any official framework integrations, but Svelte has always worked well with vanilla Javascript libraries.

$effect

If you just want to start an animation when a component mounts, you can just call it from the $effect lifecycle function. Here’s an example with the handy text splitting feature that Anime.js provides:

<script lang="ts">
  import { animate, text, stagger } from "animejs";

  let el = $state<HTMLElement | null>(null);

  $effect(() => {
    if (el) {
      const { chars } = text.split(el, {
        chars: true,
      });

      animate(chars, {
        delay: stagger(50),
        y: [30, 0],
        loop: true,
        duration: 300,
        loopDelay: 1000,
        alternate: true,
      });
    }
  });
</script>

<div class="text-xl" bind:this={el}>Hello World</div>
Hello World

Of course you can also use $effect to react to state change.

<script lang="ts">
  import { animate, text, stagger } from "animejs";

  let forward = $state(true);

  let el = $state<HTMLElement | null>(null);

  $effect(() => {
    if (el) {
      if (forward) {
        animate(el, {
          x: 200,
          rotate: 720,
          duration: 20_000,
          ease: "outCirc",
        });
      } else {
        animate(el, {
          x: -200,
          rotate: -720,
          duration: 20_000,
          ease: "outCirc",
        });
      }
    }
  });
</script>

<div>
  <div class="mb-4">
    <label for="forward">Forward</label>
    <input id="forward" name="forward" type="checkbox" bind:checked={forward} />
  </div>
  <div class="h-[40px] w-[40px] bg-amber-400" bind:this={el}></div>
</div>

Attachments

Attachments are a recent Svelte feature that allows you to run a function when an element is mounted to the DOM. This makes it easy to animate elements on entrance.

I thought you could also use the return callback to animate on removal, but it seems that the callback is only called after the element is removed from the DOM.

<script lang="ts">
  import { type Attachment } from "svelte/attachments";
  import { animate } from "animejs";

  let value = $state(0);

  const animeAttachment: Attachment = (el) => {
    const animation = animate(el, {
      y: [100, 0],
      opacity: [0, 1],
      scale: [0.5, 1],
      duration: 1000,
    });
  };
</script>

<div>
  <div>
    <p>Value: {value}</p>
    <input type="range" min="0" max="100" bind:value />
  </div>
  <div class="flex gap-2 h-[50px] absolute sm:left-1/4 left-0">
    {#each { length: 10 } as _, idx}
      {#if value >= (idx + 1) * 10}
        <div
          class="rounded-full w-[40px] h-[40px] bg-green-400 text-center"
          {@attach animeAttachment}
        >
          {(idx + 1) * 10}
        </div>
      {/if}
    {/each}
  </div>
</div>

Value: 0

You could also use it to animate all children of a component when it mounts.

<script lang="ts">
  import { type Attachment } from "svelte/attachments";
  import { animate, stagger } from "animejs";

  const animeAttachment: Attachment = (el) => {
    animate(el.children, {
      y: [100, 0],
      x: stagger(80, { start: -250 }),
      rotate: stagger(10, { start: -20 }),
      opacity: [0, 1],
      scale: [0.5, 1],
      duration: 800,
      delay: stagger(500),
      loop: true,
      loopDelay: 1000,
    });
  };
</script>

<div class="relative" {@attach animeAttachment}>
  {#each { length: 5 } as _, idx}
    <div class="bg-white p-3 rounded-md w-[80px] h-[100px] absolute border">
      Card {idx}
    </div>
  {/each}
</div>
Card 0
Card 1
Card 2
Card 3
Card 4

Transitions

If you want to animate on removal as well, you should opt for Svelte transitions. When creating a custom transition function, you can return a tick function that has parameters t and u. t is the progress of the transition, moving from 0 to 1 on entrance and 1 to 0 on exit. u is 1 - t.

Anime.js animations have a seek method which accepts a time to move the animation to. Together, you can compose them nicely like so:

<script lang="ts">
  import { animate } from "animejs";
  import type { TransitionConfig } from "svelte/transition";

  let todoLength = $state(0);

  function animeTransition(
    node: HTMLElement,
    params: { duration?: number },
    options: { direction: "in" | "out" | "both" }
  ): TransitionConfig {
    const duration = params.duration || 1000;
    const animation = animate(node, {
      opacity: [0, 1],
      x: [100, 0],
      autoplay: false,
      duration,
    });

    return {
      duration,
      tick(t, u) {
        animation.seek(t * duration);
      },
    };
  }
</script>

<div>
  <div class="flex items-center gap-3 mb-4">
    <button class="p-2" onclick={() => (todoLength += 1)}>Add Todo</button>
    <button class="p-2" onclick={() => (todoLength -= 1)}> Remove Todo </button>
    <button class="p-2" onclick={() => (todoLength = 0)}> Clear Todos </button>
  </div>
  <ul class="h-[200px]">
    {#each { length: todoLength } as _, idx}
      <li transition:animeTransition={{ duration: 300 }}>
        Todo {idx + 1}
      </li>
    {/each}
  </ul>
</div>

I feel like Anime.js and Svelte mesh quite well. Feels magical and easy to create nice animations tied in with Svelte primitives. There’s more that can be done that I haven’t explored yet, like you can probably compose with Anime.js timelines in powerful ways.

Check out the Anime.js documentation and try it out yourself!