Supermouse is a physics-based custom cursor engine for the web that tracks the pointer, hides the native OS cursor, provides API to native controls, and exposes a small plugin architecture so you can build or install composable cursors and cursor effects in your code.
You can read the full documentation here
pnpm add @supermousejs/core
npm install @supermouse/coreSupermouse itself does not render a cursor as it is not a component system, nor does it render a default stub.
The core is essentially a headless system that provides pointer tracking, state, physics, and a plugin lifecycle. You must provide a renderer, either through a plugin or through your own implementation like in the example below.
import { Supermouse } from '@supermousejs/core'
const dot = document.createElement('div')
Object.assign(dot.style, {
width: '8px',
height: '8px',
borderRadius: '50%',
background: 'red',
position: 'absolute',
transform: 'translate(-50%, -50%)',
pointerEvents: 'none'
})
const mouse = new Supermouse({
plugins: [
{
name: 'red-dot',
install(mouse) {
| `autoDisableOnMobile` | `true` | Checks if the device has a fine pointer. Independent of `enableTouch`. |
| `ignoreOnNative` | `"auto"` | `"tag"` = check HTML tags only (fast). `"css"` = check computed `cursor` style (slow). `"auto"` = both. `null` = never show the native cursor. |
| `cacheCursorStyle` | `false` | Caches cursor style data per element. **Off by default** — see below. |
| `hideCursor` | `true` | Whether the core hides the native cursor. |
| `hideOnLeave` | `true` | Hides the cursor when the pointer leaves the browser window. |
| `container` | `document.body` | The area where the instance is active. Default is the full page. |
| `zIndex` | `9999` | The stack order of the cursor stage. Increase this if overlays cover the cursor. |
| `dataPrefix` | `"supermouse"` | A prefix for `data-*` attributes. Prevents conflicts between instances. |
| `rules` | — | A map of selectors to interaction data. The core adds this data to `state.interaction` on hover. |
| `resolveInteraction` | — | A custom function to set interaction data. This bypasses `rules` and data attributes. |
| `plugins` | — | Plugins to install when you create the instance. |
| `autoStart` | `true` | Set to `false` to prevent automatic start. Call `.start()` when ready. |
### A Note on `cacheCursorStyle`
When `ignoreOnNative` is `"css"` or `"auto"`, the core reads the `cursor` style of each element using `getComputedStyle()`.
You can make the core cache this data so the core does not read the style again when you hover the same element. This can help performance, but it is disabled by default for use in reactive frameworks like Vue/React, in which case you would **not use the cache if your application changes element styles.** because the element stays in the DOM, but its `cursor` style can change. The cache will keep the old data and Supermouse will then read the wrong `cursor` style.
Use the cache only when:
- Elements do not change their `cursor` style (such as with plain HTML), or
- You know that `"css"` or `"auto"` is slow in your application.
In most cases, you do not need the cache. The core reads the style only when you hover an element. It does not read the style on each frame.
## API
```ts
const mouse = new Supermouse(options?);
mouse.state // MouseState. Read this in plugins. Do not change it outside Input or the tick loop.
mouse.options // All options with defaults applied
mouse.container // The DOM element that plugins render into
mouse.isEnabled // Returns `true` if the instance processes input
mouse.enable() // Start input processing. Hide the native cursor. Snap to the last known pointer position.
mouse.disable() // Stop input processing. Show the native cursor. Reset the state.
mouse.suspend() // Pause input and hide the stage. Do not change native cursor CSS. Use this when another instance takes control.
mouse.resume() // Resume input and show the stage. Snap to the live pointer to prevent a sweep from a stale position.
mouse.setNativeCursor("hide" | "show" | "auto") // Force the native cursor state. This overrides auto-detection.
mouse.use(plugin) // Install a plugin
mouse.getPlugin(name) // Get a plugin by name
mouse.enablePlugin(name) // Enable a disabled plugin
mouse.disablePlugin(name) // Disable a plugin
mouse.togglePlugin(name) // Enable a plugin if it is disabled. Disable it if it is enabled.
mouse.registerHoverTarget(selector) // Add a selector at runtime. The core detects hover on this selector and hides the native cursor for it.
mouse.start() // Start the animation loop if it is stopped
mouse.step(time) // Advance one frame manually
mouse.destroy() // Remove all listeners, DOM elements, and pluginsinterface SupermousePlugin {
name: string;
priority?: number;
isEnabled?: boolean;
element?: HTMLElement;
install?(instance: Supermouse): void;
update?(instance: Supermouse, deltaTime: number): void;
destroy?(instance: Supermouse): void;
onEnable?(instance: Supermouse): void;
onDisable?(instance: Supermouse): void;
}When a plugin crashes in update(), the core catches the error and runs its recovery mechanism so the rest of the instance continues to run.
The core gives all plugins the same state object. The core does not make a copy for each plugin. This helps performance. But you must know which properties you can change.
Avoid changing these properties in a plugin:
pointerisDownisHoverisNativehoverTargetinteractionreducedMotion
Only Input changes these properties.
Avoid changing these properties unless you must:
targetsmoothvelocityangle
The core tick loop changes these properties. If your plugin must change them (for example, a magnet effect), set a high priority number. A high number makes your plugin run after the core loop. Then your changes will stay.
You can change these properties:
shape- Properties you add to
InteractionState
If your plugin reads a property, your plugin can write to that property.
const mouse = new Supermouse({
rules: {
"[data-magnetic]": { magnetic: true }
}
});<button data-supermouse-magnetic="0.4">Hover me</button>Both feed into state.interaction, merged (rules first, then matching
data-{dataPrefix}-* attributes on the same element, which can override or
extend the rule).
Supermouse.js is supported by all modern browsers.
Any bug fixes, performance improvement or docs improvement are welcome. Before adding new effects or features to core, read CONTRIBUTING.md
MIT
maintained by Whitestar14