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

Usage

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

  1. One canvas, no DOM particles. A single <canvas> paints every particle each frame. No per-particle DOM nodes — particle count scales without re-renders.
  2. CSS-pixel coordinate space. The canvas backing store is multiplied by devicePixelRatio and the 2D context is setTransform-ed once on resize. All particle math stays in CSS pixels — sharp on retina, no blur.
  3. 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.
  4. 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}.
  5. Color resolves once. currentColor is read from getComputedStyle(canvas).color at mount, so a text-cb-accent (or any text-*) class on a parent drives the tint.
  6. Reduced motion paints once. Under prefers-reduced-motion: reduce, the canvas paints a single static frame and never starts the RAF loop.
  7. Pauses when hidden. A visibilitychange listener cancels the RAF loop when the tab is hidden and resumes it on return — no CPU spent on offscreen frames.

Props

PropTypeDefaultDescription
particleCountnumber30Number of particles drifting across the canvas.
speednumber0.3Maximum per-axis drift speed in CSS px / frame.
radiusnumber3Maximum particle radius in CSS px. The halo paints to 3 × this.
connectionDistancenumber150CSS px — particles closer than this draw a hairline between them. 0 disables.
parallaxStrengthnumber0.01How strongly the cursor parallaxes particles. 0 disables.
colorstring"currentColor"Any CSS color. Resolves currentColor from the canvas's CSS at mount.
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.
  • Under prefers-reduced-motion: reduce the 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 and currentColor resolution.