Composables

useAnimeScroll

Drive animations from scroll position with AnimeJS onScroll().
Buffered

useAnimeScroll wraps the onScroll function from AnimeJS. It plays or scrubs an animation as an element scrolls through its container. Pass the result as an animation's autoplay.

nanime creates the observer once the component mounts and reverts it on unmount. It rebuilds the observer when target or container points to a new element.

Usage

import { createDrawable } from '#nanime/proxies/svg'
import type { DrawableSVGGeometry } from '#nanime/types'

const THREAD = 'M 0 100 C 300 20 560 180 860 100 S 1420 18 1720 104 S 2150 192 2300 120'

const panel = useTemplateRef<HTMLElement>('panel')
const track = useTemplateRef<HTMLElement>('track')
const thread = useTemplateRef<SVGPathElement>('thread')

const scroll = useAnimeScroll({
  container: panel,
  target: track,
  axis: 'x',
  enter: 'left left',
  leave: 'right right',
  sync: true,
})

const drawable = computed<DrawableSVGGeometry | null>(() =>
  thread.value ? createDrawable(thread.value)[0] ?? null : null,
)

useAnimate([drawable], {
  draw: ['0 0', '0 1'],
  ease: 'linear',
  autoplay: scroll,
})
View on GitHub

Signature

function useAnimeScroll(
  parameters?: AnimeScrollParams
): BufferedProxyReturns<ScrollObserver>

Parameters

parameters
AnimeScrollParams
See the AnimeJS scroll documentation for the options.
target and container also accept template refs and component refs, and a new element rebuilds the observer. enter, leave, repeat and axis also accept a ref or computed ref, and a change updates the observer in place. A function that reads a ref does not.

Returns

Returns a buffered proxy around the AnimeJS ScrollObserver. Its methods and properties are in the AnimeJS scroll documentation. The members nanime changes:

MemberTypeNotes
link(animation)(animation) => thisanimation also accepts the value returned by useAnimate or useWaapiAnimate. The link survives a rebuild.
progressnumberRead it after mount. It doesn't update a template.

The observer is created when the component mounts. link(), refresh() and revert() can be called straight from setup(), and run at that point. An observer drives one animation, so create a useAnimeScroll for each animation you pass it to.

Caveats

Pass the template ref, not its value

// wrong: reads template ref during setup before mount
const section = useTemplateRef('section')
const scroll = useAnimeScroll({ target: section.value })

// right: pass the ref itself so it resolves after mount
const scroll = useAnimeScroll({ target: section })

Template refs evaluate to null during setup before the DOM renders. Passing section.value reads it immediately as null. AnimeJS then falls back to document.body as the tracked target instead of the intended element. Pass the ref itself so target and container resolve after mount.

Pass a ref, not a function that reads one

const isMobile = ref(false)

// wrong: the observer never sees isMobile change
const scroll = useAnimeScroll({ enter: () => isMobile.value ? 'bottom top' : 'center top' })

// right: a computed ref updates the observer in place
const enter = computed(() => isMobile.value ? 'bottom top' : 'center top')
const scroll = useAnimeScroll({ enter })

A function for enter, leave, repeat or axis is an AnimeJS callback. AnimeJS calls it with the observer when it refreshes, on resize for example, so refs read inside it are not tracked. Pass a ref or computed ref to update the observer when a value changes.

Read properties after mount

// wrong: progress is undefined before mount
const scroll = useAnimeScroll({ target: section })
console.log(scroll.progress) // undefined

// right: read it after mount, or in a callback
onMounted(() => {
  console.log(scroll.progress)
})

The observer is created after the component mounts and resolves target. Chainable methods such as link() are queued until then, but property reads return undefined. Read properties after mount or inside a callback such as onUpdate.

Properties do not update templates

// wrong: progress never updates inside a template interpolation
// <p>{{ scroll.progress }}</p>

// right: track progress via a ref updated in onUpdate
const progress = ref(0)
const scroll = useAnimeScroll({
  onUpdate: observer => progress.value = observer.progress,
})

Interpolating {{ scroll.progress }} displays the initial value and never updates. The returned proxy tracks the observer instance rather than internal numeric state, so scrolling triggers no component re-renders. Update a ref inside the onUpdate callback to display live scroll progress.

Type reference

See also

Copyright © 2026