All components

Dot Field

Backgrounds

A canvas dot grid that reacts around the cursor, at a density SVG can't reach. The per-frame cost is bounded by the influence radius rather than the dot count: only the dirty rect is cleared and redrawn, and dots are batched into 16 paths by quantised alpha instead of one fill each. DPR-correct on retina, and the rAF loop is genuinely cancelled — offscreen, backgrounded, or simply settled.

0 dotsDPR 1hover to measure
Spacing · 10px
Radius · 140px
Mode
Direction

Drop spacing to 8 or below to push past 5,000 dots and watch the fps hold — the per-frame cost is bounded by the influence radius, not the grid size. Scroll the field out of view and the rAF loop is cancelled outright, not left spinning.

Installation

Terminal
npx shadcn@latest add https://ui.saumyarex.xyz/r/dot-field.json

Props

PropTypeDefaultDescription
spacingnumber18Grid pitch in CSS pixels — this is what drives the dot count. Changing it rebuilds the grid; everything else retunes the running loop in place.
mode"displace" | "scale" | "brighten""displace"What the cursor does to nearby dots. All three route through the same smoothed slots, so they settle with identical timing.
radiusnumber140Influence radius in CSS pixels. Also sets the size of the dirty rect, so it — not the grid — is what determines per-frame cost.
strengthnumberPeak effect at the cursor. Units depend on `mode`: pixels of push for `displace` (default 14), radius multiplier for `scale` (3), alpha multiplier for `brighten` (2.8). Defaults per mode, since 14px of push and a 14× radius are not the same request.
attractbooleanfalsePull dots toward the cursor instead of pushing them away. `displace` mode only.
falloff(t: number) => numberNormalised distance 0…1 → intensity 0…1. Defaults to a raised cosine, flat at both ends so neither the peak nor the radius edge creases.
dotSize / opacitynumber1.2 / 0.35Resting dot radius in CSS pixels, and resting alpha.
colorstringAny canvas-valid color. Defaults to the inherited `currentColor`, re-resolved when the theme attribute flips — a canvas can't inherit it live the way SVG does.
smoothingnumber0.09Settle time constant in seconds. Applied as `1 − exp(−dt/τ)`, so the feel is frame-rate independent.
onMeasure(info: { dots, cols, rows, dpr }) => voidFires on layout changes only, never per frame — safe to `setState` in. Useful for surfacing the live dot count.

Dependencies

clsxtailwind-merge