mouse-follower: Animated Cursor Effects with Vanilla JS & GSAP.js

Category: Javascript , Recommended | August 24, 2026
AuthorCuberto
Last UpdateAugust 24, 2026
LicenseMIT
Tags
Views0 views
mouse-follower: Animated Cursor Effects with Vanilla JS & GSAP.js

mouse-follower is a JavaScript custom cursor library that adds a GSAP-animated element that follows the mouse pointer. The follower can change into text, an SVG icon, an image, or a video as the pointer moves across different parts of the page.

You can define Hover behavior in JavaScript or directly in HTML data attributes. Custom state classes handle changes such as color, size, visibility, and hover feedback, while sticky and skew controls create more pronounced pointer interactions.

Features

  • Smooth GSAP-based pointer tracking.
  • Text and SVG icon cursor content.
  • Image and video hover previews.
  • Custom state classes for hover effects.
  • Sticky motion around fixed elements.
  • Velocity-based cursor skew.
  • HTML data attributes for common interactions.
  • Configurable movement, easing, timing, and state behavior.
  • Event hooks for visibility, state changes, rendering, and cleanup.
  • Built-in browser and module distributions.

How To Use It

Installation

Direct Browser Setup

Load the mouse-follower stylesheet, GSAP v3, and the mouse-follower script in the document.

<link
  rel="stylesheet"
  href="https://unpkg.com/mouse-follower@1/dist/mouse-follower.min.css"
>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
<script src="https://unpkg.com/mouse-follower@1/dist/mouse-follower.min.js"></script>
<script>
  const cursor = new MouseFollower();
</script>

NPM and Module Projects

The npm setup requires both packages. Register the imported GSAP instance before creating the cursor. The compiled cursor stylesheet is available from the package distribution.

npm install gsap mouse-follower
import MouseFollower from 'mouse-follower';
import { gsap } from 'gsap';
import 'mouse-follower/dist/mouse-follower.min.css';
MouseFollower.registerGSAP(gsap);
const cursor = new MouseFollower();

Basic Usage

The default constructor creates the cursor elements inside document.body. Links and buttons automatically receive the built-in pointer state, while iframes hide the follower under the default state-detection setup.

<a href="/portfolio">View Portfolio</a>
<button type="button">Open Menu</button>
<script>
  const cursor = new MouseFollower();
</script>

Control Cursor Content from HTML

  • data-cursor: Adds one or more cursor state classes.
  • data-cursor-show: Shows a cursor that starts hidden.
  • data-cursor-text: Displays text inside the follower.
  • data-cursor-icon: Displays an icon from the configured SVG sprite.
  • data-cursor-icon-img: Displays an icon from an image URL.
  • data-cursor-img: Displays an image preview.
  • data-cursor-video: Displays and plays a video.
  • data-cursor-stick: Pulls the follower toward the hovered element or another element selector.
<a href="/case-study" data-cursor-text="View">
  Case Study
</a>
<article data-cursor-img="/images/project-preview.webp">
  Product Design
</article>
<a href="/showreel" data-cursor-video="/media/showreel.mp4">
  Watch Showreel
</a>
<button data-cursor-stick>
  Contact
</button>

Create Custom Cursor States

A cursor state is a class on the root follower element. stateDetection maps those classes to page selectors, while data-cursor handles individual elements.

Define the state names during initialization, then style those states in your stylesheet.

const cursor = new MouseFollower({
  stateDetection: {
    '-pointer': 'a, button',
    '-project': '.project-card',
    '-hidden': 'iframe, input'
  }
});
<div class="project-card">
  Project Preview
</div>
<div data-cursor="-inverse">
  Dark Section
</div>
.mf-cursor.-project::before {
  transform: scale(0.8);
  background: #ff4d00;
}
.mf-cursor.-inverse {
  color: #fff;
}

Set Text, Icons, and Media from JavaScript

const cursor = new MouseFollower();
const projectLink = document.querySelector('.project-link');
projectLink.addEventListener('mouseenter', function () {
  cursor.setText('Explore');
});
projectLink.addEventListener('mouseleave', function () {
  cursor.removeText();
});

For SVG sprites, set the sprite file and naming rules before calling setIcon().

const cursor = new MouseFollower({
  iconSvgSrc: '/assets/icons/sprite.svg',
  iconSvgClassName: 'site-icons',
  iconSvgNamePrefix: '-'
});
const nextButton = document.querySelector('.next-project');
nextButton.addEventListener('mouseenter', function () {
  cursor.setIcon('arrow-right');
});
nextButton.addEventListener('mouseleave', function () {
  cursor.removeIcon();
});

An existing SVGElement works with setIcon(), while setIconImg() accepts an image URL.

const arrow = document.createElementNS(
  'http://www.w3.org/2000/svg',
  'svg'
);
arrow.setAttribute('viewBox', '0 0 24 24');
arrow.innerHTML = '<path d="M5 12h14M13 6l6 6-6 6"/>';
cursor.setIcon(arrow);
// Or use an external file.
cursor.setIconImg('/icons/arrow-right.svg');

Image previews use setImg() and removeImg().

const card = document.querySelector('.featured-project');
card.addEventListener('mouseenter', function () {
  cursor.setImg('/images/featured-project.webp');
});
card.addEventListener('mouseleave', function () {
  cursor.removeImg();
});

Video previews use a muted, looping video element created by mouse-follower.

const reelLink = document.querySelector('.reel-link');
reelLink.addEventListener('mouseenter', function () {
  cursor.setVideo('/media/reel-preview.mp4');
});
reelLink.addEventListener('mouseleave', function () {
  cursor.removeVideo();
});

Add Sticky Motion

Sticky motion moves the follower toward the center of another element. The calculated center comes from getBoundingClientRect(), and the effect is intended for fixed elements.

const cursor = new MouseFollower();
const control = document.querySelector('.floating-control');
const button = document.querySelector('.floating-button');
control.addEventListener('mouseenter', function () {
  cursor.setStick(button);
});
control.addEventListener('mouseleave', function () {
  cursor.removeStick();
});

A selector also works inside data-cursor-stick.

<div class="floating-control" data-cursor-stick="#contact-button">
  <button id="contact-button">Contact</button>
</div>

Adjust Cursor Skew

Skew uses the distance and direction between the actual pointer and the animated follower position.

const cursor = new MouseFollower();
const hero = document.querySelector('.hero');
hero.addEventListener('mouseenter', function () {
  cursor.setSkewing(2.5);
});
hero.addEventListener('mouseleave', function () {
  cursor.removeSkewing();
});

Start with a Hidden Follower

visible: false keeps the follower hidden until show() runs or an element with data-cursor-show receives the mouse.

const cursor = new MouseFollower({
  visible: false
});
const demo = document.querySelector('.interactive-demo');
demo.addEventListener('mouseenter', function () {
  cursor.show();
  cursor.setText('Drag');
});
demo.addEventListener('mouseleave', function () {
  cursor.removeText();
  cursor.hide();
});

The equivalent markup uses data-cursor-show.

<div data-cursor-show data-cursor-text="Drag">
  Interactive Demo
</div>

Configuration Options

  • el (string | HTMLElement | null): Uses an existing root cursor element. Default: null.
  • container (string | HTMLElement): Parent element for the generated cursor. Default: document.body.
  • eventsTarget (string | HTMLElement): Element that receives cursor mouse events. Default: document.body.
  • className (string): Root cursor class. Default: mf-cursor.
  • innerClassName (string): Inner cursor class. Default: mf-cursor-inner.
  • textClassName (string): Text container class. Default: mf-cursor-text.
  • mediaClassName (string): Media container class. Default: mf-cursor-media.
  • mediaBoxClassName (string): Inner media box class. Default: mf-cursor-media-box.
  • iconSvgClassName (string): Class assigned to SVG sprite icons. Default: mf-svgsprite.
  • iconSvgNamePrefix (string): Class-name prefix for SVG sprite icons. Default: -.
  • iconSvgSrc (string): SVG sprite file URL. Default: an empty string.
  • iconImgClassName (string): Class assigned to image-based icons. Default: mf-cursor-icon.
  • dataAttr (string | false): Prefix used for cursor data attributes. Set false to disable this binding. Default: cursor.
  • hiddenState (string): Hidden-state class. Default: -hidden.
  • textState (string): Text-mode class. Default: -text.
  • iconState (string): Icon-mode class. Default: -icon.
  • activeState (string | false): State applied while the mouse button is held down. Set false to disable it. Default: -active.
  • mediaState (string): Image and video state class. Default: -media.
  • stateDetection (object | false): Maps cursor states to page selectors. Set false to disable selector-based detection.
  • visible (boolean): Initial cursor visibility. Default: true.
  • visibleOnState (boolean): Shows the follower when a state becomes active and hides it after the state is removed. Default: false.
  • speed (number): GSAP tween duration used for cursor movement. Default: 0.55.
  • ease (string): GSAP easing expression for movement. Default: expo.out.
  • overwrite (boolean): Controls GSAP tween overwriting during mouse movement. Default: true.
  • skewing (number): Normal cursor skew factor. Default: 0.
  • skewingText (number): Skew factor for text mode. Default: 2.
  • skewingIcon (number): Skew factor for icon mode. Default: 2.
  • skewingMedia (number): Skew factor for image and video mode. Default: 2.
  • skewingDelta (number): Base velocity multiplier used by the skew calculation. Default: 0.001.
  • skewingDeltaMax (number): Maximum velocity contribution to the skew calculation. Default: 0.15.
  • stickDelta (number): Strength of the pull toward a sticky element. Default: 0.15.
  • showTimeout (number): Delay before the follower becomes visible. Default: 0.
  • hideOnLeave (boolean): Hides the follower when the mouse leaves the configured event area. Default: true.
  • hideTimeout (number): Delay before the internal visible state changes after hiding. Default: 300.
  • hideMediaTimeout (number): Delay before hidden media content is cleared. Default: 300.
  • initialPos (number[]): Initial [x, y] coordinates. The default coordinates place the cursor outside the viewport.

API Methods

// Register the GSAP instance for module builds.
MouseFollower.registerGSAP(gsap);
// Create a follower.
const cursor = new MouseFollower(options);
// Show or hide it.
cursor.show();
cursor.hide();
// Toggle visibility. Pass true or false to force the result.
cursor.toggle(true);
// Add, remove, or toggle state classes.
cursor.addState('-featured');
cursor.removeState('-featured');
cursor.toggleState('-featured', true);
// Change the skew factor and restore its configured value.
cursor.setSkewing(2.5);
cursor.removeSkewing();
// Pull the follower toward an element and release it.
cursor.setStick('.floating-button');
cursor.removeStick();
// Set and clear text.
cursor.setText('View');
cursor.removeText();
// Set an SVG sprite icon or SVGElement.
cursor.setIcon('arrow-right');
// Set an image-based icon.
cursor.setIconImg('/icons/arrow-right.svg');
// Clear icon mode.
cursor.removeIcon();
// Insert an HTMLElement into the media container.
cursor.setMedia(mediaElement);
// Clear media mode.
cursor.removeMedia();
// Set and clear an image.
cursor.setImg('/images/preview.webp');
cursor.removeImg();
// Set and clear a video.
cursor.setVideo('/media/preview.mp4');
cursor.removeVideo();
// Register and remove event handlers.
cursor.on('show', handler);
cursor.off('show', handler);
cursor.off('show');
// Remove the instance and its registered listeners.
cursor.destroy();

Events

mouse-follower uses its own on() and off() event API. These callbacks receive the cursor instance as the first argument. State events receive the affected state as the second argument.

show and hide fire when their corresponding methods run. The configured timeouts affect the later visibility update.

cursor.on('show', function (cursor) {
  console.log('Show requested');
});
cursor.on('hide', function (cursor) {
  console.log('Hide requested');
});
cursor.on('addState', function (cursor, state) {
  console.log('State added:', state);
});
cursor.on('removeState', function (cursor, state) {
  console.log('State removed:', state);
});
cursor.on('render', function (cursor) {
  // Runs during an active render tick.
});
cursor.on('destroy', function (cursor) {
  console.log('Cursor destroyed');
});

Styling and Customization

The generated markup uses a small set of predictable classes. Constructor options can rename each one when an existing CSS naming system needs different selectors.

  • mf-cursor
  • mf-cursor-inner
  • mf-cursor-text
  • mf-cursor-media
  • mf-cursor-media-box
  • mf-svgsprite
  • mf-cursor-icon

Alternatives

You Might Be Interested In:


Leave a Reply