npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@juleshry/vue-animejs

v1.0.1

Published

Vue 3 composables for Anime.js v4 — reactive animations that integrate naturally with Vue's reactivity system and component lifecycle.

Downloads

470

Readme

🚀 Overview

vue-animejs wraps Anime.js v4 as idiomatic Vue 3 composables. It integrates with Vue's reactivity system — pass a ref as a target or option and the animation updates automatically. Lifecycle cleanup is handled for you.

Before — raw Anime.js in a Vue component:

<script setup lang="ts">
import { useTemplateRef, onMounted, onUnmounted } from 'vue'
import { animate } from 'animejs'

const el = useTemplateRef<HTMLElement>('el')
let animation

onMounted(() => {
  if (!el.value) {
    console.warn('Targets element is null or undefined')
    return
  }
  
  animation = animate(el.value, { translateX: 250, duration: 800 })
})

onUnmounted(() => {
  animation?.cancel()
})
</script>

<template>
  <div ref="el" />
</template>

After — with vue-animejs:

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useAnimate } from '@juleshry/vue-animejs'

const el = useTemplateRef<HTMLElement>('el')

useAnimate(el, { translateX: 250, duration: 800 })
</script>

<template>
  <div ref="el" />
</template>

📦 Requirements

  • Vue 3.5+
  • Anime.js 4+
  • @vueuse/core 14+

🦄 Composables

| Composable | Description | |------------------|---------------------------------------------------| | useAnimate | Animate a DOM target with reactive params | | useRawAnimate | Thin wrapper around Anime.js animate() with ref unwrapping | | useTimer | Drive a timer with full playback control | | useTimeline | Sequence multiple animations on a shared timeline | | useWaapi | Animate via the Web Animations API (WAAPI) | | useAnimatable | Create a reactive animatable object | | useDraggable | Make a DOM element draggable with full control | | useLayout | Animate DOM layout changes (reorder, add, remove) | | useScope | Create an Anime.js scope with lifecycle management | | useSvg | SVG utilities: morph paths and motion path | | useSvgDrawable | Animate SVG stroke drawing with the draw property | | useText | Split text into animatable lines, words, and chars |

🚀 Usage

useAnimate

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useAnimate } from '@juleshry/vue-animejs'

const el = useTemplateRef<HTMLElement>('el')

const { play, pause } = useAnimate(el, {
  translateX: 250,
  duration: 800,
  easing: 'easeInOutQuad',
})
</script>

<template>
  <div ref="el" />
  <button @click="play">Play</button>
  <button @click="pause">Pause</button>
</template>

useTimer

<script setup lang="ts">
import { useTimer } from '@juleshry/vue-animejs'

const { play, pause } = useTimer({
  duration: 3000,
  onUpdate: (self) => console.log(self.currentTime),
})
</script>

useTimeline

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useTimeline } from '@juleshry/vue-animejs'

const box = useTemplateRef<HTMLElement>('box')

const { add, play } = useTimeline({ loop: true })

add(box, { translateX: 100 })
add(box, { translateY: 50 }, '+=200')
play()
</script>

useRawAnimate

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useRawAnimate } from '@juleshry/vue-animejs'

const el = useTemplateRef<HTMLElement>('el')

// Returns the raw Anime.js JSAnimation instance directly
const animation = useRawAnimate(el, { translateX: 250, duration: 800 })
</script>

<template>
  <div ref="el" />
</template>

useAnimatable

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useAnimatable } from '@juleshry/vue-animejs'

const el = useTemplateRef<HTMLElement>('el')

const { animatable } = useAnimatable(el, {
  x: 0,
  opacity: 1,
})
</script>

useWaapi

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useWaapi } from '@juleshry/vue-animejs'

const el = useTemplateRef<HTMLElement>('el')

const { play, pause } = useWaapi(el, {
  translateX: 250,
  duration: 800,
  easing: 'easeInOutQuad',
})
</script>

<template>
  <div ref="el" />
  <button @click="play">Play</button>
  <button @click="pause">Pause</button>
</template>

useSvg

Note: morphTo requires a live DOM element. Add each target as a hidden <path>, pass the ref directly to morphTo, and wrap in a computeduseTimeline.add() accepts MaybeRef<AnimationParams> and resolves the computed after mount, when the target elements are available.

<script setup lang="ts">
import { computed, useTemplateRef } from 'vue'
import { useSvg, useTimeline } from '@juleshry/vue-animejs'

const shape = useTemplateRef<SVGPathElement>('shape')
const t_circle = useTemplateRef<SVGPathElement>('t-circle')
const t_diamond = useTemplateRef<SVGPathElement>('t-diamond')

const { morphTo } = useSvg()

const { add } = useTimeline({
  loop: true,
  defaults: { duration: 1200, easing: 'easeInOutCubic' },
  loopDelay: 500,
})

add(shape, computed(() => ({ d: morphTo(t_circle) })))
  .add(shape, computed(() => ({ d: morphTo(t_diamond) })), '+=500')
</script>

<template>
  <svg viewBox="0 0 100 100">
    <path ref="shape" d="M 50 15 C 78 8, 95 28, 90 52 C 85 76, 68 90, 50 88 C 32 90, 15 76, 10 52 C 5 28, 22 8, 50 15 Z" />
    <path ref="t-circle" d="M 50 10 C 72 10, 90 28, 90 50 C 90 72, 72 90, 50 90 C 28 90, 10 72, 10 50 C 10 28, 28 10, 50 10 Z" visibility="hidden" />
    <path ref="t-diamond" d="M 50 5 C 54 32, 68 32, 95 50 C 68 68, 54 68, 50 95 C 46 68, 32 68, 5 50 C 32 32, 46 32, 50 5 Z" visibility="hidden" />
  </svg>
</template>

useSvgDrawable

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useAnimate, useSvgDrawable } from '@juleshry/vue-animejs'

const circle_el = useTemplateRef<SVGCircleElement>('circle')

const { drawable } = useSvgDrawable(circle_el)

// draw values are normalised: 0 = hidden, 1 = fully drawn
useAnimate(drawable, {
  draw: ['0 0', '0.5 1', '0 1'],
  duration: 1200,
  easing: 'easeInOutQuad',
})
</script>

<template>
  <svg viewBox="0 0 100 100">
    <circle ref="circle" cx="50" cy="50" r="40" fill="none" stroke="currentColor" stroke-width="4" />
  </svg>
</template>

useDraggable

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useDraggable } from '@juleshry/vue-animejs'

const el = useTemplateRef<HTMLElement>('el')

const { disable, enable } = useDraggable(el, {
  onGrab: () => console.log('grabbed'),
  onDrag: () => console.log('dragging'),
  onRelease: () => console.log('released'),
})
</script>

<template>
  <div ref="el" />
  <button @click="disable">Disable</button>
  <button @click="enable">Enable</button>
</template>

useLayout

Note: Vue's DOM updates are asynchronous. Use await nextTick() between the state change and animate() so Anime.js reads the updated positions.

<script setup lang="ts">
import { nextTick, ref, useTemplateRef } from 'vue'
import { useLayout } from '@juleshry/vue-animejs'

const list = useTemplateRef<HTMLElement>('list')
const { record, animate } = useLayout(list, {})

const items = ref([1, 2, 3, 4, 5])

async function shuffle() {
  record()
  items.value = [...items.value].sort(() => Math.random() - 0.5)
  await nextTick()
  animate({ duration: 600 })
}
</script>

<template>
  <ul ref="list">
    <li v-for="item in items" :key="item">{{ item }}</li>
  </ul>
  <button @click="shuffle">Shuffle</button>
</template>

When you mutate the DOM directly with classList.toggle, the change is synchronous — no nextTick needed:

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useLayout } from '@juleshry/vue-animejs'

const container = useTemplateRef<HTMLElement>('container')
const { update } = useLayout(container, {})

function toggleLayout() {
  update(() => {
    container.value!.classList.toggle('grid')
  }, { duration: 600 })
}
</script>

<template>
  <div ref="container" class="grid">
    <div v-for="i in 6" :key="i" class="item" />
  </div>
  <button @click="toggleLayout">Toggle layout</button>
</template>

useScope

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useAnimate, useScope } from '@juleshry/vue-animejs'

const el = useTemplateRef<HTMLElement>('el')

const { add, revert } = useScope({ mediaQuery: '(prefers-reduced-motion: no-preference)' })

// Animations added via add() are automatically reverted when the scope reverts
// or when the component unmounts
add(scope => {
  useAnimate(el, { translateX: 250, duration: 800 })
})
</script>

<template>
  <div ref="el" />
  <button @click="revert">Revert</button>
</template>

useText

Note: words, chars, and lines are ComputedRef<HTMLElement[]> — they populate after mount and can be passed directly to useAnimate.

<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { useAnimate, useText } from '@juleshry/vue-animejs'
import { stagger } from 'animejs'

const el = useTemplateRef<HTMLElement>('el')

const { words, chars } = useText(el, { words: true, chars: true })

// words and chars are ComputedRef<HTMLElement[]> — pass them directly as targets
useAnimate(words, {
  y: ['100%', '0%'],
  duration: 600,
  delay: stagger(50),
})
</script>

<template>
  <p ref="el">Hello, world!</p>
</template>

👨‍🚀 Contributors

🌸 Thanks

🧱 Contributing

See the Contributing Guide.

🗺️ TODO

  • [ ] Allow reactive refs inside option objects
  • [ ] Finish doc website
  • [ ] Add components
  • [ ] Add directives

📄 License

MIT © 2025-present Jules Hery