useImageTrail
Appending an element per pointer move allocates dozens of DOM nodes a second, which shows up as garbage collection spikes and layout jitter once the pointer moves quickly. useImageTrail keeps a fixed pool of elements and recycles them, animated through the Web Animations API on the compositor thread.
The useImageTrail hook attaches to a bounded container to create a recycled image trail along the cursor's trajectory.
The hook creates a fixed pool of maxActive DOM image elements once upon mount. When the pointer travels the configured distance threshold (spacing), the next image in the pool is reused and animated.
Flights are executed via the browser's native Web Animations API (WAAPI) on compositor layers (transform and opacity). This provides 120 FPS performance with zero garbage collection overhead and zero CPU frame loops between movements.
◆Pooled Compositor Image Flights
Quick start
Attach the returned ref to a container element with relative positioning and supply your image source array.
All trail images are marked aria-hidden and pointer-events: none, ensuring zero interference with underlying UI links.
DOM Node Pooling & WAAPI Execution
How memory pooling and browser compositor execution eliminate performance bottlenecks during continuous pointer motion.
Rather than appending and removing elements per spawn, the hook allocates maxActive image elements once during mount. When the cursor travels spacing pixels, the oldest active animation is cancelled and its DOM element is repositioned to the current cursor coordinate.
Keyframes are dispatched directly to element.animate() using GPU-accelerated translate(), scale(), and opacity. The main thread remains completely idle during flights.
API Reference
Syntax
Binds a container element to a cursor-following image trail powered by pooled DOM nodes and WAAPI.
Parameters
| Parameters.field | Type | Default | Description |
|---|---|---|---|
| options.images | string[] | (none) | Array of image URLs cycled in sequential order. |
| options.size | number | 160 | Rendered width in pixels for each trail image. |
| options.spacing | number | 90 | Cursor travel distance in pixels required to spawn the next image. |
| options.duration | number | 900 | Lifespan in milliseconds for each image flight animation. |
| options.maxActive | number | 10 | Maximum number of simultaneous active images in the pre-allocated pool. |
Return Value
Ref attached to the bounding container element.
Properties & State
| Properties.field | Type | Description |
|---|---|---|
| ref | RefObject<T | null> | DOM ref attached to the trail container. |
DOM Node Teardown & Pointer Unbinding
On unmount, all pre-allocated image elements and pointer event listeners are removed cleanly.
Lifecycle & Invariant Guarantees
- →Zero GC allocation during movement: Recycles a pre-allocated array of DOM elements, eliminating memory allocation spikes during active mouse movement.
- →Compositor execution: Dispatches animations to the browser Web Animations API, running transforms entirely on GPU compositor threads.
- →Complete accessibility safety: Trail elements carry aria-hidden='true', empty alt attributes, and pointer-events: none, ensuring zero interference with screen readers.
- →Touch and reduced-motion safety: Automatically disables itself on touch devices and when prefers-reduced-motion is active.
◆Production Examples
Battle-tested production patterns ready to copy directly into your codebase.
Full-bleed hero banner with generous image size and spacing for visual impact.
When NOT to use this
Rules & Gotchas
- →Curate optimized thumbnail assets: Compress image sources to their rendered display dimensions (e.g. 160px width) rather than loading raw camera photos.
- →Tune spacing before pool size: Increasing spacing distance creates more deliberate, legible visual flights than crowding high numbers of images.
- →Treat the trail as purely visual decoration: Ensure underlying text and navigation elements remain completely functional without requiring the trail.
- →Use keys to retune options dynamically: Options are read once upon mount to construct the DOM pool; update the React key when changing configurations.