useAnimeLayout
useAnimeLayout wraps the createLayout
function from AnimeJS. It records the position and size of every element
inside a container. After a change, it animates each element from the
recorded state to the new one.
nanime creates the layout once the container mounts and reverts it on
unmount. It rebuilds the layout only when the container element changes or
the parameters change in value. Callbacks such as onComplete are left out
of that comparison.
Usage
Size animates together with position. Here one box grows at a time and the others move aside:
const row = useTemplateRef('row')
const expanded = ref(1)
const layout = useAnimeLayout(row, { duration: 500 })
function expandNext() {
layout.patch(() => (expanded.value = (expanded.value % 5) + 1))
}
<template>
<div ref="row" class="flex flex-wrap items-start gap-2">
<div
v-for="n in 5"
:key="n"
class="rounded-lg bg-primary"
:class="expanded === n ? 'h-20 w-32' : 'size-10'"
/>
</div>
</template>
Signature
function useAnimeLayout(
target: MaybeRef<AnimeLayoutTargets>,
parameters?: MaybeRefOrGetter<AutoLayoutParams>
): BufferedProxyReturns<NanimeLayout>
Parameters
ref or a getter. The layout rebuilds when a
value changes. See the
AnimeJS layout documentation
for the options.Returns
Returns a buffered proxy around the AnimeJS AutoLayout.
Its methods and properties are in the
AnimeJS layout documentation.
The members nanime adds or changes:
| Member | Type | Notes |
|---|---|---|
patch(callback, params?) | (callback: () => unknown, params?: LayoutAnimationParams) => Promise<void> | Added by nanime, and the one to use for Vue state. It calls record(), awaits callback, waits for Vue's nextTick(), then calls animate(params). The promise resolves when the animation finishes. |
record() | () => this | Can be called straight from setup(), and runs once the layout is created. |
The layout is created when the container mounts. Call patch() and read
other members after mount.
Caveats
Use patch(), not update(), for Vue state
// wrong: animate() runs before Vue has patched the DOM
layout.update(() => items.value.reverse())
// right: patch() waits for nextTick() before animating
await layout.patch(() => items.value.reverse())
AnimeJS update() records, runs the callback and animates in one
synchronous call. Vue applies state changes to the DOM on the next tick, so
animate() compares the old DOM with itself and nothing moves. patch()
awaits nextTick() between the two steps. update() still works for
direct DOM changes, such as appendChild.
Read properties after mount
// wrong: timeline is undefined before mount
const layout = useAnimeLayout(grid)
console.log(layout.timeline) // undefined
// right: read it after mount
onMounted(() => {
console.log(layout.timeline)
})
The layout is created after the container mounts. record() is queued
until then, but other members, patch() included, return undefined.
Call patch() from an event handler or after mount.
Type reference
type AnimeLayoutTargets = DOMTargetSelector | MaybeElementRef<HTMLElement | SVGElement | VueInstance | null> | null | undefined
type NanimeLayout = AutoLayout & {
patch: (callback: () => unknown, params?: LayoutAnimationParams) => Promise<void>
}
type BufferedProxyReturns<T> = Exclude<UnwrapNestedRefs<T>, null | undefined>
See also
<AnimeTransitionGroup>: animatesv-forlists on every change, with nopatch()calls- AnimeJS layout documentation: upstream layout options such as
swapAt