Getting Started

Performance

Make nanime do less work, starting with the changes that save the most.

Each change on this page makes nanime do less work, starting with the one that saves the most.

Add deps to list and layout groups

Pass deps to every <AnimeTransitionGroup> and <AnimeLayoutGroup>:

CardList.vue
<template>
  <AnimeTransitionGroup :deps="[]">
    <Card v-for="card in cards" :key="card.id" :card="card" />
  </AnimeTransitionGroup>
</template>

Without deps, a group measures every element inside it each time it re-renders, even when nothing moved. With :deps="[]", an <AnimeTransitionGroup> measures only when items are added, removed or reordered. Add the values that change the size of items, such as a view toggle: :deps="[view]".

An <AnimeLayoutGroup> animates only when a value in deps changes, so list the state that changes its layout, as in :deps="[expanded]". With :deps="[]", it never animates.

Put fewer elements inside group items

Each move measures every element inside the group, including the text and buttons inside each item. In a group of 12 cards with about 20 elements each, one move measures all 240 elements.

When the items of an <AnimeTransitionGroup> only enter and leave, turn moves off. The group then measures nothing:

Feed.vue
<template>
  <AnimeTransitionGroup :move-animation="false">
    <Post v-for="post in posts" :key="post.id" :post="post" />
  </AnimeTransitionGroup>
</template>

Run simple animations off the main thread

Use useWaapiAnimate for transform and opacity animations, such as modals, dropdowns and page entrances. The browser runs them off the main thread, so they stay smooth while the page is busy, for example during hydration. useWaapiAnimate also adds less to the bundle: 5.5 KB with gzip, against 13.3 KB for useAnimate.

Modal.vue
<script setup lang="ts">
const modal = useTemplateRef('modal')

useWaapiAnimate(modal, {
  transform: ['translateY(20px) scale(0.95)', 'translateY(0px) scale(1)'],
  opacity: [0, 1],
  duration: 300,
  ease: 'cubic-bezier(0.2, 0, 0, 1)',
})
</script>

Use useAnimate for what the browser cannot run on its own:

  • SVG morphing with morphTo
  • Motion paths with createMotionPath
  • SVG line drawing with createDrawable
  • Text scrambling with scrambleText, or useScrambleText
  • Plain JavaScript objects and numbers

Use useAnimatable for values that change constantly

For a value that changes many times a second, such as the cursor position, use useAnimatable. It creates one animation up front, and each call tweens from the current value:

Cursor.vue
<script setup lang="ts">
const circle = useTemplateRef('circle')

const animatable = useAnimatable(circle, {
  translateX: 0,
  translateY: 0,
  ease: 'outExpo',
  duration: 300,
})

function onMouseMove(event: MouseEvent) {
  animatable.translateX(event.clientX)
  animatable.translateY(event.clientY)
}
</script>

Split text by words or lines

Split headings and short callouts with useSplitText, not long text. Each piece becomes its own element, so an article split into characters adds thousands of elements to the page. Split by words or lines when the effect allows it:

Heading.vue
<script setup lang="ts">
import { stagger } from '#nanime/utils'

const heading = useTemplateRef('heading')
const { words } = useSplitText(heading, { words: true })

useAnimate(words, {
  translateY: [20, 0],
  opacity: [0, 1],
  delay: stagger(60),
  duration: 400,
})
</script>

<template>
  <h2 ref="heading">Ship it today</h2>
</template>

Target elements with template refs

Pass a template ref instead of a selector string:

Card.vue
<script setup lang="ts">
const card = useTemplateRef('card')

useAnimate(card, { scale: [0.9, 1], opacity: [0, 1], duration: 400 })
</script>

<template>
  <div ref="card">Content</div>
</template>

A selector such as '.card' matches every .card on the page. When the component renders more than once, each copy animates the cards of all the others too. With three copies on the page, each card runs three animations.

Animate transform and opacity

Animating width, height or top makes the browser recompute the layout on every frame. Prefer translateX, translateY, scale, rotate and opacity, which the browser only has to composite. Animations and performance explains the difference.

Check what each API adds to the bundle

nanime adds code only to the pages that call a composable or render a component. The table lists what each one adds to the client bundle:

Composable or componentMinifiedMinified + gzip
useSplitText11.1 KB4.2 KB
useWaapiAnimate13.9 KB5.5 KB
useAnimatable34.3 KB13.1 KB
useAnimate35.0 KB13.3 KB
<AnimeTransition>37.5 KB14.5 KB
useScrambleText37.8 KB14.8 KB
useAnimeTimeline39.7 KB15.0 KB
useAnimeScroll47.2 KB17.5 KB
useDraggable58.8 KB20.1 KB
<AnimeLayoutGroup>68.5 KB23.8 KB
<AnimeTransitionGroup>75.1 KB26.4 KB
useAnimate and useAnimeTimeline on the same page add 40.8 KB minified, not 74.7 KB (35.0 KB + 39.7 KB), because they share the AnimeJS animation engine.

Imports from #nanime/composables add the same code as auto-imports.

Measured with AnimeJS 4.5.0 and Nuxt 4.5.

Copyright © 2026