Ambient Canvas
A canvas-rendered ambient backdrop. A handful of particles drift slowly, bounce off the edges, and draw a hairline web between near neighbours — the kind of motion that sits behind hero copy without ever pulling attention to itself. Drop it inside any relative container; it fills the parent and is pointer-events: none, so foreground content stays interactive.
ambient
Customize
Density
30
Motion
0.3
150px
Color
Installation
npx shadcn@latest add https://craftbits.dev/r/ambient-canvas.jsonUsage
import { AmbientCanvas } from "@craft-bits/core";
<section className="relative h-screen w-full text-cb-accent">
<AmbientCanvas particleCount={30} speed={0.3} />
{/* foreground content */}
</section>Understanding the component
- One canvas, no DOM particles. A single
<canvas>paints every particle each frame. No per-particle DOM nodes — particle count scales without re-renders. - CSS-pixel coordinate space. The canvas backing store is multiplied by
devicePixelRatioand the 2D context issetTransform-ed once on resize. All particle math stays in CSS pixels — sharp on retina, no blur. - Soft glow per particle. Each particle paints twice: an outer radial gradient out to
3 ×its core radius, then the opaque core on top. The halo blends additively over any background. - Cursor parallax. Mouse position relative to the canvas centre nudges every particle each frame. The effect scales by index so deeper particles drift further — cheap depth illusion. Disable with
parallaxStrength={0}. - Color resolves once.
currentColoris read fromgetComputedStyle(canvas).colorat mount, so atext-cb-accent(or anytext-*) class on a parent drives the tint. - Reduced motion paints once. Under
prefers-reduced-motion: reduce, the canvas paints a single static frame and never starts the RAF loop. - Pauses when hidden. A
visibilitychangelistener cancels the RAF loop when the tab is hidden and resumes it on return — no CPU spent on offscreen frames.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
particleCount | number | 30 | Number of particles drifting across the canvas. |
speed | number | 0.3 | Maximum per-axis drift speed in CSS px / frame. |
radius | number | 3 | Maximum particle radius in CSS px. The halo paints to 3 × this. |
connectionDistance | number | 150 | CSS px — particles closer than this draw a hairline between them. 0 disables. |
parallaxStrength | number | 0.01 | How strongly the cursor parallaxes particles. 0 disables. |
color | string | "currentColor" | Any CSS color. Resolves currentColor from the canvas's CSS at mount. |
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. - Under
prefers-reduced-motion: reducethe RAF loop never starts. A single static frame is painted so the canvas isn't blank; no animation, no pointer listeners. - Pauses on
document.visibilitychange(tab switch) to save CPU; on resume the RAF loop picks up where it left off.
Credits
- Extracted from:
terminal-dreams(src/components/cookbook/AmbientCanvas.tsx). Re-architected from a fixed full-viewport canvas to a parent-bounded canvas, DPR-aware, with pointer/visibility cleanup andcurrentColorresolution.