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

vue-hover-effect

v0.0.1-stable

Published

> WebGL-powered image hover transition effects for Vue 3, built on top of [Three.js](https://threejs.org/) and [GSAP](https://gsap.com/). Inspired by [Robin Delaporte's hover-effect](https://github.com/robin-dela/hover-effect).

Readme

vue-hover-effect

WebGL-powered image hover transition effects for Vue 3, built on top of Three.js and GSAP. Inspired by Robin Delaporte's hover-effect.

Features

  • GPU-accelerated displacement map transitions between images
  • Two ready-to-use components: single two-image transition and multi-image carousel
  • Manual control via next / previous slot methods
  • Video texture support
  • Fully typed props — TypeScript autocompletion out of the box
  • SSR / Nuxt compatible — Three.js and GSAP are lazy-loaded client-side only

Installation

# npm
npm install vue-hover-effect

# pnpm
pnpm add vue-hover-effect

# yarn
yarn add vue-hover-effect

Peer dependencies

Three.js and GSAP are peer dependencies. Install them alongside the package:

npm install three gsap

Usage

SingleHoverEffect

Transitions between two images on hover (or manually via next / previous).

<script setup lang="ts">
import { SingleHoverEffect } from 'vue-hover-effect'
</script>

<template>
  <SingleHoverEffect
    image1="/images/photo-a.webp"
    image2="/images/photo-b.webp"
    displacement-image="/displacements/displacement.webp"
  />
</template>

With custom size

Use the distortion-class prop to apply your own CSS class to the canvas container:

<template>
  <SingleHoverEffect
    image1="/images/photo-a.webp"
    image2="/images/photo-b.webp"
    displacement-image="/displacements/displacement.webp"
    distortion-class="my-effect"
  />
</template>

<style>
.my-effect {
  width: 600px;
  height: 400px;
}
</style>

With manual controls

Use the controllers slot to wire up your own buttons. The slot exposes funcs.goNext() and funcs.goPrev():

<template>
  <SingleHoverEffect
    image1="/images/photo-a.webp"
    image2="/images/photo-b.webp"
    displacement-image="/displacements/displacement.webp"
    :hover="false"
    distortion-class="my-effect"
  >
    <template #controllers="{ funcs }">
      <button @click="funcs.goPrev()">Previous</button>
      <button @click="funcs.goNext()">Next</button>
    </template>
  </SingleHoverEffect>
</template>

With all options

<template>
  <SingleHoverEffect
    image1="/images/photo-a.webp"
    image2="/images/photo-b.webp"
    displacement-image="/displacements/displacement.webp"
    :images-ratio="1.5"
    :intensity="0.8"
    :speed-in="1.2"
    :speed-out="0.9"
    easing="power2.out"
    :hover="true"
  />
</template>

Props

| Prop | Type | Required | Default | Description | |---|---|---|---|---| | image1 | string | Yes | — | URL of the first image | | image2 | string | Yes | — | URL of the second image | | displacementImage | string | No | CDN map #1 | URL of the displacement map texture | | imagesRatio | number | No | 1.0 | Aspect ratio of the images (width / height) | | intensity | number | No | 1 | Distortion intensity fallback for both images | | intensity1 | number | No | 1 | Distortion intensity for the first image | | intensity2 | number | No | 1 | Distortion intensity for the second image | | angle | number | No | Math.PI / 4 | Displacement rotation angle fallback | | angle1 | number | No | — | Rotation angle for the first image | | angle2 | number | No | — | Rotation angle for the second image | | speed | number | No | — | Transition speed fallback for both directions | | speedIn | number | No | 1.6 | Duration (seconds) of the enter transition | | speedOut | number | No | 1.2 | Duration (seconds) of the exit transition | | hover | boolean | No | true | Trigger transitions automatically on hover | | easing | string | No | 'expo.out' | GSAP easing string | | video | boolean | No | false | Treat image1 / image2 as <video> sources | | distortionClass | string | No | '' | CSS class applied to the canvas container element |


MultipleHoverEffect

Cycles through an array of images with displacement transitions. Requires at least two images.

<script setup lang="ts">
import { MultipleHoverEffect } from 'vue-hover-effect'
</script>

<template>
  <MultipleHoverEffect
    :images="[
      '/images/photo-a.webp',
      '/images/photo-b.webp',
      '/images/photo-c.webp',
    ]"
    displacement-image="/displacements/displacement.webp"
  />
</template>

With manual controls

<template>
  <MultipleHoverEffect
    :images="[
      '/images/photo-a.webp',
      '/images/photo-b.webp',
      '/images/photo-c.webp',
    ]"
    displacement-image="/displacements/displacement.webp"
    :hover="false"
    distortion-class="my-gallery"
  >
    <template #controllers="{ funcs }">
      <button @click="funcs.goPrev()">Previous</button>
      <button @click="funcs.goNext()">Next</button>
    </template>
  </MultipleHoverEffect>
</template>

Props

| Prop | Type | Required | Default | Description | |---|---|---|---|---| | images | string[] | Yes | — | Array of image URLs (minimum 2) | | displacementImage | string | No | CDN map #1 | URL of the displacement map texture | | imagesRatio | number | No | 1.0 | Aspect ratio of the images (width / height) | | intensity1 | number | No | 1 | Distortion intensity for the current image | | intensity2 | number | No | 1 | Distortion intensity for the next image | | angle1 | number | No | Math.PI / 4 | Rotation angle for the current image | | angle2 | number | No | — | Rotation angle for the next image | | speedIn | number | No | 1.2 | Duration (seconds) of the forward transition | | speedOut | number | No | 1.0 | Duration (seconds) of the backward transition | | hover | boolean | No | false | Advance to next image on hover | | easing | string | No | 'expo.out' | GSAP easing string | | distortionClass | string | No | '' | CSS class applied to the canvas container element |


Displacement maps

The displacementImage prop is optional. When omitted, the component uses a built-in displacement map served from jsDelivr CDN — no setup required.

The package ships 16 displacement textures. You can reference any of them via the exported DISPLACEMENT_URLS array:

import { DISPLACEMENT_URLS } from 'vue-hover-effect'
// DISPLACEMENT_URLS[0]  →  map #1  (default)
// DISPLACEMENT_URLS[5]  →  map #6
// ...up to DISPLACEMENT_URLS[15]
<template>
  <SingleHoverEffect
    image1="/images/photo-a.webp"
    image2="/images/photo-b.webp"
    :displacement-image="DISPLACEMENT_URLS[4]"
  />
</template>

Host displacement maps yourself

If you prefer to serve the files locally (offline support, no CDN dependency), copy them from the package to your project's public folder:

cp -r node_modules/vue-hover-effect/dist/lib-images/displacements public/displacements

Then pass the local path:

<SingleHoverEffect
  image1="/images/photo-a.webp"
  image2="/images/photo-b.webp"
  displacement-image="/displacements/3.webp"
/>

TypeScript

All prop types are exported and can be imported for use in your own components or composables:

import type {
  SingleHoverEffectProps,
  MultiImageEffectProps,
  SingleHoverEffectOptions,
  MultiImageEffectOptions,
  SingleHoverEffectController,
  MultiImageEffectController,
} from 'vue-hover-effect'

SingleHoverEffectProps and MultiImageEffectProps are the component-facing types (without parent, which is handled internally). SingleHoverEffectOptions / MultiImageEffectOptions are the full option types accepted by the underlying factory functions.


Nuxt

The components are SSR-safe out of the box. Three.js and GSAP are dynamically imported inside onMounted, so they are never included in the server bundle and no WebGL code runs during server-side rendering.

No extra configuration is needed. Just import and use:

<!-- pages/index.vue -->
<script setup lang="ts">
import { SingleHoverEffect } from 'vue-hover-effect'
</script>

<template>
  <SingleHoverEffect
    image1="/images/photo-a.webp"
    image2="/images/photo-b.webp"
    displacement-image="/displacements/displacement.webp"
  />
</template>

If you prefer an explicit client-only guarantee, Nuxt's built-in <ClientOnly> wrapper also works:

<template>
  <ClientOnly>
    <SingleHoverEffect
      image1="/images/photo-a.webp"
      image2="/images/photo-b.webp"
      displacement-image="/displacements/displacement.webp"
      :hover="true"
    />
  </ClientOnly>
</template>

Global registration

You can register both components globally in your Vue app:

// main.ts
import { createApp } from 'vue'
import { SingleHoverEffect, MultipleHoverEffect } from 'vue-hover-effect'
import App from './App.vue'

const app = createApp(App)
app.component('SingleHoverEffect', SingleHoverEffect)
app.component('MultipleHoverEffect', MultipleHoverEffect)
app.mount('#app')

For Nuxt, register them in a plugin:

// plugins/vue-hover-effect.client.ts
import { SingleHoverEffect, MultipleHoverEffect } from 'vue-hover-effect'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.component('SingleHoverEffect', SingleHoverEffect)
  nuxtApp.vueApp.component('MultipleHoverEffect', MultipleHoverEffect)
})

Using the factory functions directly

The underlying factory functions are also exported for headless usage without the Vue components:

import { createSingleHoverEffect, createMultiImageEffect } from 'vue-hover-effect'

const controller = createSingleHoverEffect({
  parent: document.querySelector('#my-container') as HTMLElement,
  image1: '/images/photo-a.webp',
  image2: '/images/photo-b.webp',
  displacementImage: '/displacements/displacement.webp',
  speedIn: 1.2,
  speedOut: 0.9,
  hover: true,
})

// Manual control
controller?.next()     // transition to image2
controller?.previous() // transition back to image1
controller?.resize()   // recalculate on container resize

License

MIT — Sultonkhon