Pixel Trail
A canvas-rendered cursor trail. Each grid cell flashes to full opacity the moment the cursor crosses it, then fades back to transparent. Drop it inside any relative container — it fills the parent and is pointer-events: none, so foreground content stays interactive.
move the cursor
Customize
Grid
14px
Fade
500ms
0ms
Mode
Installation
npx shadcn@latest add https://craftbits.dev/r/pixel-trail.jsonUsage
import { PixelTrail } from "@craft-bits/core";
<section className="relative h-screen w-full text-cb-accent">
<PixelTrail pixelSize={12} fadeDuration={400} />
{/* foreground content */}
</section>Understanding the component
- One canvas, no DOM grid. The source renders a
<div>per cell with framer-motion controls — fine at low density, fatal at thousands of cells. We collapse to a single<canvas>and aMap<cellKey, litAt>. - Lit cells age. Each RAF frame computes
alpha = 1 - (now - litAt - delay) / fadeDurationper cell, clamps, paints, and drops cells once they're fully faded. - Bresenham interpolation. A fast cursor flick can skip many cells between RAF frames. With
interpolateon, we rasterise a line from the previous pointer to the current one so the trail stays continuous. - Color resolves once.
currentColoris read fromgetComputedStyle(canvas).colorat mount, so the CSS text color (e.g.text-cb-accenton a parent) drives the fill. - DPR-aware. The canvas backing store is multiplied by
devicePixelRatioand the 2D context issetTransform-ed so painting uses CSS pixel coordinates — sharp on retina, no blur. - Reduced motion short-circuits. With
prefers-reduced-motion: reduce, the effect is skipped entirely — no listeners, no RAF.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
pixelSize | number | 12 | Side length of each cell, in CSS px. |
fadeDuration | number | 400 | ms for a lit pixel to fade back to invisible. |
delay | number | 0 | ms a lit pixel stays at full opacity before fading. |
color | string | "currentColor" | Any CSS color. Resolves currentColor from the canvas's CSS at mount. |
interpolate | boolean | true | Fill in cells between RAF frames when the cursor moves fast. |
className | string | — | Class applied to the underlying <canvas>. |
Accessibility
- The canvas carries
aria-hidden="true"andpointer-events: none— pure visual decoration. Foreground content keeps its full interaction surface. - Animation is fully disabled when
prefers-reduced-motion: reduceis set — no RAF loop, no event listeners. - Pauses on
document.visibilitychange(tab switch) to save CPU; on resume the trail is reset rather than catching up to stale timestamps.
Credits
- Extracted from:
terminal-dreams(src/components/interactions/PixelTrail.tsx). Re-architected from a per-cell framer-motion grid to a single canvas with timestamp-driven fade.