StringCursor Module
Translate pointer motion into CSS-driven hover states. StringCursor observes desktop mouse movement, writes normalized coordinates to targets, and keeps custom cursor elements in sync with eased values.
Activation primer: Add
string="cursor"to every element you wantStringCursorto control. Supplystring-id="your-id"when you need a stable handle for events, mirrors, orstring-copy-from; otherwise StringTune generates one automatically.
HTML Attributes
Interactive Element Attributes
These attributes are applied to elements with string="cursor" — the interactive targets that respond to hover.
| Attribute | Type | Default | Controls | Practical notes |
|---|---|---|---|---|
string-target-disable | boolean | false | Opt the element out of cursor interactions even when flagged with string="cursor" | Use on wrappers that should not respond despite inheriting the attribute |
string-target-style-disable | boolean | false | Skip writing --x/--y CSS variables to this element | Ideal when you consume only the emitted events |
string-cursor-target | string | "default" | ID(s) of cursor portal(s) to target (comma/pipe-separated, or * for all) | Links this element to specific cursor portal(s) |
string-target-class | string | "" | Class added to the target element itself while hovered | Applies to the interactive element for self-styling |
string-cursor-class | string | "" | Class added to the cursor portal element(s) while the target is hovered | Useful for theme-aware pointer styling on the custom cursor |
string-alignment | enum (start/center/end) | center | How the module normalizes pointer position across the element | center yields -1…1, start/end map to 0…1 ranges |
string-lerp | number | 0.2 | Lerp factor applied to element's coordinate smoothing (remapped to 0.05–0.65) | Larger numbers feel tighter; tweak per element for variety |
Interactive element CSS variables (written every frame unless string-target-style-disable="true"):
--x,--y— normalized coordinates based onstring-alignmentsetting
Cursor Portal Attributes
These attributes are applied to cursor portal elements with [string-cursor] or [data-string-cursor] — the custom cursor visuals that follow the pointer.
| Attribute | Type | Default | Controls | Practical notes |
|---|---|---|---|---|
string-cursor | string | "default" | Portal ID for targeting from interactive elements | Use unique IDs for different cursor styles |
string-cursor-id | string | — | Alternative attribute for explicit portal ID | Use when string-cursor value serves another purpose |
string-cursor-lerp | number | 0.75 | Independent lerp factor for this portal's smoothing (remapped to 0.05–0.65) | Override global cursor smoothing per portal |
Cursor portal CSS variables (written every frame):
--x,--y— smoothed cursor position in pixels (relative to viewport origin)--x-lerp,--y-lerp— normalized velocity (movement per 60fps frame)
Cursor portal auto-classes (managed by the module):
-showing— added immediately when any linked interactive element is hovered-show— added after 1200ms delay for progressive visual states
Module Snapshot
- Activation attribute:
string="cursor" - Per-target CSS variables:
--x,--y(normalized coordinates, written to element and its mirrors) - Cursor portal CSS variables:
--x,--y(smoothed pixels),--x-lerp,--y-lerp(normalized velocity) - Event channels:
cursor:start|move|pixel|end:<id>, plus the globalcursorevent - Pointer gating: module auto-disables on coarse pointers (touch/pen)
- Lifecycle helpers: attaches ResizeObservers, MutationObservers, and navigation listeners (beforeunload, pagehide, visibilitychange) to ensure cleanup on SPAs
- Portal system: supports multiple cursor portals with independent IDs and lerp factors
Basic Usage
import StringTune, { StringCursor } from '@fiddle-digital/string-tune';
const stringTune = StringTune.getInstance();
stringTune.use(StringCursor);
stringTune.start(60);
<!-- Cursor portal - receives smoothed cursor position -->
<div string-cursor class="custom-cursor">
<div string-cursor-content class="cursor-dot"></div>
</div>
<!-- Interactive element - tracks hover and emits normalized coordinates -->
<button string="cursor" string-cursor-class="-hovering" string-lerp="0.35" class="cta">Hover me</button>
/* Interactive element receives normalized coordinates */
.cta {
--tx: calc(var(--x, 0) * 12px);
--ty: calc(var(--y, 0) * 12px);
transform: translate(var(--tx), var(--ty));
transition: box-shadow 0.2s ease;
}
/* Cursor portal receives pixel coordinates with velocity */
.custom-cursor {
position: fixed;
inset: 0 auto auto 0;
width: 18px;
height: 18px;
margin: -9px 0 0 -9px;
pointer-events: none;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
transform: translate3d(calc(var(--x, 0) * 1px), calc(var(--y, 0) * 1px), 0);
opacity: 0;
transition: opacity 0.3s ease;
}
/* Show cursor when hovering interactive elements */
.custom-cursor.-showing {
opacity: 1;
}
/* Additional state after delay */
.custom-cursor.-show {
background: rgba(255, 100, 100, 0.8);
}
How It Works
- Hover capture: elements with
string="cursor"register mouseenter/leave listeners when they become visible via viewport tracking; Safari navigation quirks are handled with multiple lifecycle listeners. - Center sampling: the module tracks element centers via
CenterCachewith ResizeObservers, allowing coordinates to be normalized even as layouts shift. - Frame-rate compensation:
string-lerppasses through an adaptive curve and gets FPS-adjusted to maintain consistent easing regardless of refresh rate (60Hz, 120Hz, 240Hz, etc.). - CSS writes: unless disabled via
string-target-style-disable, every frame updates--x/--y(normalized) on the element and all mirrors created withstring-copy-from. - Cursor portals: elements with
[string-cursor]receive smoothed pixel coordinates (--x,--y) plus normalized velocity (--x-lerp,--y-lerp) for custom cursor styling. Multiple portals can coexist with different IDs and lerp factors. - Portal targeting: use
string-cursor-targetto link interactive elements to specific cursor portals. Supports wildcards (*), multiple IDs (comma/pipe-separated), or defaults to first available portal. - Hover classes: automatically adds
-showingclass immediately on hover, then-showclass after 1200ms delay for progressive visual states.
Event Signals
| Channel pattern | Payload | Fired when | Common uses |
|---|---|---|---|
cursor:start:<id> | null | Pointer begins moving over the element (above epsilon) | Kick off sound cues, toggle hover classes |
cursor:move:<id> | { x: number; y: number; } (normalized) | Normalized coordinates change beyond epsilon | Drive DOM transforms, shaders, or analytics samples |
cursor:pixel:<id> | { x: number; y: number; } (smoothed pixels relative to element) | Smoothed pixel offsets update beyond epsilon | Position tooltips, magnetic highlights |
cursor:end:<id> | null | Pointer stops moving (settles below epsilon) | Fade out hover embellishments |
cursor | { x: number; y: number; stepX: number; stepY: number; } | Global smoothed cursor position changes | Sync custom cursor portals, broadcast to state store |
Event payload details:
cursor:moveandcursor:pixelrespect alignment setting (center=-1…1,start/end=0…1)cursorevent provides global smoothed position in viewport pixels plus movement deltas- All events honor epsilon threshold (
0.0005) to avoid unnecessary updates
// Track normalized coordinates for element effects
stringTune.on('cursor:move:cta', ({ x, y }) => {
// x & y are normalized based on string-alignment setting
// Use for subtle parallax, tilt, or shader uniforms
});
// Track pixel offsets for precise positioning
stringTune.on('cursor:pixel:cta', ({ x, y }) => {
// x & y are smoothed pixel offsets from element's top-left
// Perfect for tooltip positioning or magnetic effects
});
// Track global cursor for state management
stringTune.on('cursor', ({ x, y, stepX, stepY }) => {
// x, y: smoothed viewport pixels
// stepX, stepY: movement deltas (useful for velocity effects)
});
Cursor Portal System
The module supports multiple independent cursor portals, each with its own ID and configuration. This allows different cursor styles for different interaction contexts.
<!-- Default cursor portal -->
<div string-cursor class="cursor-default"></div>
<!-- Special cursor portal for CTA elements -->
<div string-cursor="cta" string-cursor-lerp="0.25" class="cursor-cta"></div>
<!-- Link elements to specific portals -->
<button string="cursor" string-cursor-target="cta" string-cursor-class="-active">Call to Action</button>
<!-- Target multiple portals at once -->
<a string="cursor" string-cursor-target="default,cta">Multi-target link</a>
<!-- Target all portals -->
<div string="cursor" string-cursor-target="*">Affects all cursors</div>
Portal ID resolution:
- Reads from
string-cursor,data-string-cursor,string-cursor-id, ordata-string-cursor-id - Falls back to
"default"if no ID specified - Each portal can have independent
string-cursor-lerpfor different smoothing
Portal CSS variables:
--x,--y: smoothed cursor position in pixels (relative to viewport)--x-lerp,--y-lerp: normalized velocity (movement per 60fps frame)
Portal classes:
-showing: added immediately when any linked element is hovered-show: added after 1200ms delay (useful for delayed animations)
Advanced Examples
Multi-cursor setup with different behaviors:
<div string-cursor class="cursor-normal"></div>
<div string-cursor="drag" string-cursor-lerp="0.5" class="cursor-drag"></div>
<div string="cursor" string-cursor-target="normal">Hover element</div>
<div string="cursor" string-cursor-target="drag" string-cursor-class="-dragging">Draggable element</div>
.cursor-drag {
transform: translate3d(calc(var(--x) * 1px), calc(var(--y) * 1px), 0) scale(1.5);
}
.cursor-drag.-dragging {
transform: translate3d(calc(var(--x) * 1px), calc(var(--y) * 1px), 0) scale(2);
}
Using velocity for motion blur:
.custom-cursor {
transform: translate3d(calc(var(--x) * 1px), calc(var(--y) * 1px), 0);
filter: blur(calc(abs(var(--x-lerp, 0)) * 2px + abs(var(--y-lerp, 0)) * 2px));
}
Element-level effects with normalized coordinates:
.card {
transform: perspective(1000px) rotateY(calc(var(--x, 0) * 10deg)) rotateX(calc(var(--y, 0) * -10deg));
transition: transform 0.1s ease-out;
}
Integration with Other Modules
StringCursor works seamlessly with other StringTune modules:
<!-- Combine with StringMirror for synchronized effects -->
<div string="cursor" string-id="card-main">
<div string-copy-from="card-main" class="card-reflection"></div>
</div>
<!-- Use events with StringMagnetic for advanced interactions -->
<button string="cursor magnetic" string-lerp="0.3" string-magnetic-strength="0.8">Magnetic button</button>
Performance Notes
- Auto-disable on touch: module detects coarse pointers and disables automatically on mobile/tablet
- FPS compensation: lerp values are adjusted based on actual frame rate to maintain consistent feel across 60Hz, 120Hz, and 240Hz displays
- Epsilon threshold: movement below
0.0005is considered settled, preventing unnecessary updates - Safari navigation fixes: multiple lifecycle hooks ensure proper cleanup when navigating in SPAs
- ResizeObserver: automatically tracks element size changes for accurate center calculations
StringCursor keeps hover choreography responsive—dial the lerp per component, wire events into motion systems, use multiple cursor portals for context-aware styling, and let frame-rate compensation handle the rest.