Performance
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>:
<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:
<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.
<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, oruseScrambleText - 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:
<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:
<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:
<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 component | Minified | Minified + gzip |
|---|---|---|
useSplitText | 11.1 KB | 4.2 KB |
useWaapiAnimate | 13.9 KB | 5.5 KB |
useAnimatable | 34.3 KB | 13.1 KB |
useAnimate | 35.0 KB | 13.3 KB |
<AnimeTransition> | 37.5 KB | 14.5 KB |
useScrambleText | 37.8 KB | 14.8 KB |
useAnimeTimeline | 39.7 KB | 15.0 KB |
useAnimeScroll | 47.2 KB | 17.5 KB |
useDraggable | 58.8 KB | 20.1 KB |
<AnimeLayoutGroup> | 68.5 KB | 23.8 KB |
<AnimeTransitionGroup> | 75.1 KB | 26.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.