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

@lostisworld/svelte-interactive-cursor

v0.4.4

Published

A Svelte component for creating interactive custom cursors. Enhance user experience with customizable cursor effects and animations.

Downloads

104

Readme

InteractiveCursor Component

The InteractiveCursor is a Svelte 5 component that provides a customizable, interactive cursor effect. It dynamically changes its position and size based on user interactions within specified trigger areas. This component is ideal for enhancing user experiences with visually engaging cursor animations.

Live Demo

CHANGELOG


Installation

# npm
npm install @lostisworld/svelte-interactive-cursor

# pnpm
pnpm add @lostisworld/svelte-interactive-cursor

Features

  1. Dynamic Resizing: The cursor adjusts its size and position dynamically when hovering over elements specified in the useDataElementRect property.
  2. Scaling on Interaction: Scale transformations can be applied to the cursor when hovering over specified elements using scaleOnActive.
  3. Animation Control: Smooth animations with customizable duration and easing using the Web Animations API.
  4. Custom Icons: Allows custom rendering inside the cursor element using the children snippet.
  5. State Exposure: Exposes activeDataValue and isActive as bindable props to track cursor state in the parent.
  6. Responsive Design: Automatically disables the interactive cursor below a configurable breakpoint or when reduced motion is preferred.
  7. Reduced Motion: Dynamically responds to OS-level reduced motion changes mid-session — no page reload required.
  8. Hide Native Cursor: Optionally hides the OS cursor inside trigger areas via hideNativeCursor.
  9. Performance: Animations are throttled to one per frame via requestAnimationFrame, layout reads are cached, and the module is loaded only once across all instances.

Types

ScaleOnActiveElement

type ScaleOnActiveElement = {
	element: string; // The name of the element (value of `data-interactive-cursor`).
	scaleMultiplicator?: number; // Scale factor to apply when the element is active. Default: 3.
};

InteractiveCursorOptions

interface InteractiveCursorOptions {
	defaultSize?: number; // Default cursor size in pixels. Default: 32.
	scaleOnActive?: ScaleOnActiveElement[]; // Elements with scale factors. Default: [].
	duration?: number; // Animation duration in milliseconds. Default: 500.
	easing?: string; // CSS easing for the animation. Default: 'linear'.
	useDataElementRect?: string[]; // Elements that trigger cursor resizing. Default: [].
	hideNativeCursor?: boolean; // Hide the OS cursor inside trigger areas. Default: false.
}

Usage

Basic Setup

<script lang="ts">
	import InteractiveCursor from '@lostisworld/svelte-interactive-cursor';
</script>

<div data-interactive-cursor-area>
	<button data-interactive-cursor="btn">Hover me!</button>
</div>

<InteractiveCursor
	defaultSize={40}
	duration={300}
	scaleOnActive={[{ element: 'btn', scaleMultiplicator: 2 }]}
	useDataElementRect={['btn']}
/>

Advanced Example

<script lang="ts">
	import InteractiveCursor, {
		type ScaleOnActiveElement,
		type ActiveDataValue
	} from '@lostisworld/svelte-interactive-cursor';

	let currentCursorState: ActiveDataValue = $state({ activeDataName: '', activeDataElement: null });
	let cursorIsActive = $state(false);

	const scaleOnActive: ScaleOnActiveElement[] = [
		{ element: 'image' },
		{ element: 'video', scaleMultiplicator: 4 },
		{ element: 'link' },
		{ element: 'mixblend', scaleMultiplicator: 8 }
	];

	const customCursorProps = [
		{ data: 'image', icon: '<svg>...</svg>' },
		{ data: 'video', icon: '<svg>...</svg>', cursorClass: 'bg-red-500 text-white' },
		{ data: 'link', icon: '<svg>...</svg>', cursorClass: 'bg-sky-500 text-white' },
		{ data: 'tablist', cursorClass: 'rounded-none outline outline-2 outline-purple-500' }
	];
</script>

<section data-interactive-cursor-area>
	<div data-interactive-cursor="image">Image</div>
	<div data-interactive-cursor="video">Video</div>
	<div data-interactive-cursor="link">Link</div>
</section>

<InteractiveCursor
	bind:activeDataValue={currentCursorState}
	bind:isActive={cursorIsActive}
	{scaleOnActive}
	useDataElementRect={['tablist']}
	duration={400}
	easing="linear"
	breakpoint={1024}
	class="rounded-full flex items-center justify-center {currentCursorState.activeDataName === ''
		? 'bg-white text-black'
		: (customCursorProps.find((s) => s.data === currentCursorState.activeDataName)?.cursorClass ??
			'bg-white text-black')}"
>
	{#each customCursorProps as { icon, data }}
		{#if data === currentCursorState.activeDataName && icon}
			{@html icon}
		{/if}
	{/each}
</InteractiveCursor>

Component Props

| Prop | Type | Default | Description | | -------------------- | -------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------- | | defaultSize | number | 32 | Default cursor size in pixels. | | scaleOnActive | ScaleOnActiveElement[] | [] | Elements and their scale factors when hovered. | | duration | number | 500 | Animation duration in milliseconds. | | easing | string | 'linear' | CSS easing function for the animation (e.g. 'ease-out', 'cubic-bezier(0.4,0,0.2,1)'). | | useDataElementRect | string[] | [] | Element names for which the cursor resizes and aligns to their bounding rectangle. | | hideNativeCursor | boolean | false | Hides the OS cursor inside trigger areas when true. | | breakpoint | number | 1024 | Minimum viewport width (px) below which the cursor is disabled. | | class | string | '' | Additional CSS classes to apply to the cursor element. | | children | Snippet | undefined | Custom content rendered inside the cursor. | | activeDataValue | ActiveDataValue bindable | { activeDataName: '', activeDataElement: null } | Bindable. Tracks the active data-interactive-cursor name and its DOM element. | | isActive | boolean bindable | false | Bindable. true while the cursor is inside a trigger area. |


Data Attributes

| Attribute | Description | | --------------------------------- | ----------------------------------------------------------------------------------------- | | data-interactive-cursor-area | Marks a container as a cursor tracking zone. Mouse enter/leave is tracked here. | | data-interactive-cursor="value" | Marks a child element with a name used to match scaleOnActive and useDataElementRect. |

<div data-interactive-cursor-area>
	<div data-interactive-cursor="image">Image Element</div>
	<div data-interactive-cursor="card">Card Element</div>
</div>

Styling

Default Classes

  • .lw-interactive-cursor — base cursor styles (fixed position, hidden by default).
  • .lw-interactive-cursor.active — applied while the cursor is inside a trigger area.

CSS Variables

| Variable | Default | Description | | ------------ | ----------- | ------------------------ | | --size | 32px | Driven by defaultSize. |

Example

.lw-interactive-cursor {
	background-color: white;
	border-radius: 50%;
}
.lw-interactive-cursor.active {
	background-color: blue;
}

Advanced: interactiveCursor function

For headless / programmatic use, the core function is exported directly:

import { interactiveCursorFN } from '@lostisworld/svelte-interactive-cursor';

const cursor = interactiveCursorFN(cursorElement, {
	defaultSize: 32,
	scaleOnActive: [{ element: 'btn', scaleMultiplicator: 2 }],
	duration: 500,
	easing: 'linear',
	useDataElementRect: ['card'],
	hideNativeCursor: false
});

cursor.init();

// destroy:
cursor.destroy();

Returned object

| Member | Type | Description | | ----------------- | ---------------------------- | --------------------------------------------- | | isActive | boolean (readonly) | Whether the cursor is inside a trigger area. | | activeDataValue | ActiveDataValue (readonly) | Current active element name and reference. | | init() | () => void | Attach event listeners and start tracking. | | destroy() | () => void | Remove event listeners and cancel animations. |


Performance

  • RAF throttlingmousemove is throttled to one animation call per frame via requestAnimationFrame, preventing excessive work at 200+ events/sec.
  • Cached layout readsoffsetWidth/offsetHeight are read once at init; getBoundingClientRect() is only called when the hovered element changes.
  • O(1) scale lookupscaleOnActive is converted to a Map at init for constant-time lookups per frame.
  • Single module import — the core module is loaded once across all component instances on the page via a shared Promise cache.
  • will-change: transform — applied only on .active to promote the element to a compositor layer while animating.
  • Resize/scroll rect invalidation — the cached bounding rect is recalculated on resize and scroll so useDataElementRect positions remain accurate.

Notes

  • Reduced Motion: Automatically disabled on mount if the user prefers reduced motion. Also responds to OS-level changes mid-session without a page reload.
  • Responsive: Disabled below the configured breakpoint (default 1024px).
  • Always place data-interactive-cursor-area on the parent container of your interactive elements.

Contributing

Contributions are welcome!

  1. Fork the repository.
  2. Create a new branch for your feature or bugfix.
  3. Commit your changes with clear and descriptive messages.
  4. Submit a pull request.

License

This project is licensed under the MIT License.