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.json

Usage

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

  1. 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 a Map<cellKey, litAt>.
  2. Lit cells age. Each RAF frame computes alpha = 1 - (now - litAt - delay) / fadeDuration per cell, clamps, paints, and drops cells once they're fully faded.
  3. Bresenham interpolation. A fast cursor flick can skip many cells between RAF frames. With interpolate on, we rasterise a line from the previous pointer to the current one so the trail stays continuous.
  4. Color resolves once. currentColor is read from getComputedStyle(canvas).color at mount, so the CSS text color (e.g. text-cb-accent on a parent) drives the fill.
  5. DPR-aware. The canvas backing store is multiplied by devicePixelRatio and the 2D context is setTransform-ed so painting uses CSS pixel coordinates — sharp on retina, no blur.
  6. Reduced motion short-circuits. With prefers-reduced-motion: reduce, the effect is skipped entirely — no listeners, no RAF.

Props

PropTypeDefaultDescription
pixelSizenumber12Side length of each cell, in CSS px.
fadeDurationnumber400ms for a lit pixel to fade back to invisible.
delaynumber0ms a lit pixel stays at full opacity before fading.
colorstring"currentColor"Any CSS color. Resolves currentColor from the canvas's CSS at mount.
interpolatebooleantrueFill in cells between RAF frames when the cursor moves fast.
classNamestringClass applied to the underlying <canvas>.

Accessibility

  • The canvas carries aria-hidden="true" and pointer-events: none — pure visual decoration. Foreground content keeps its full interaction surface.
  • Animation is fully disabled when prefers-reduced-motion: reduce is 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.