ParticleField

An interactive particle ocean with interchangeable formations and custom point clouds.

Move over the field to attract particles. Custom helix uses supplied XYZ positions. Motion follows your device preference until you choose Play.

Installation

bunx --bun shadcn@latest add undefined/r/ParticleField.json

Usage

Install with the registry command above in a React app with Tailwind, shadcn and its cn utility configured. The registry installs [email protected] and @vgpu/[email protected] and copies the renderer source and MIT attribution. WGSL is prepared inside the lazy renderer; no Next.js or Vite shader-loader configuration is required. Import from your own components directory.

import { ParticleField } from "@/components/ParticleField";

<ParticleField shape="ocean" color="#64748b" className="min-h-[620px]">
  <div className="px-6 py-40 text-center">
    <h1 className="text-5xl">An ocean of possibilities.</h1>
    <a href="/explore" className="mt-8 inline-block rounded-lg border px-5 py-3">
      Explore
    </a>
  </div>
</ParticleField>

Children remain clickable above the decorative canvas. Pointer coordinates use the container bounds, including when the cursor is over foreground content. Choose sphere, torus or ribbon to change the overall formation. The individual particles remain circular. Give the container a height or sufficient padding.

Custom formations

Pass an object with a packed Float32Array of XYZ positions. The renderer centers the points and uniformly fits their bounds, preserving their proportions. Model units do not matter. Keep the object stable; replace it when the shape changes. Do not mutate its array in place. Custom arrays must contain 1–262144 finite XYZ triplets. An invalid array reveals the fallback and logs a diagnostic; remount after correcting it.

"use client";
import { useMemo } from "react";
import { ParticleField } from "@/components/ParticleField";

export function Helix() {
  const shape = useMemo(() => {
    const positions = new Float32Array(8192 * 3);
    for (let i = 0; i < 8192; i++) {
      const t = i / 8191;
      const angle = t * Math.PI * 10;
      positions.set([Math.cos(angle), (t - 0.5) * 4, Math.sin(angle)], i * 3);
    }
    return { positions };
  }, []);
  return <ParticleField shape={shape} className="h-[440px]" />;
}

SVG, text and mesh sampling can produce the same point format. Their conversion is a recipe-level concern. Custom point clouds retain their supplied formation and share cursor attraction; this API does not invent continuous custom motion, simulate fluid on arbitrary meshes, or morph between unrelated point sets.

API Reference

PropTypeDefaultDescription
shape"ocean" | "sphere" | "torus" | "ribbon" | { positions: Float32Array }"ocean"Overall particle formation. Custom point objects need a client boundary.
colorCSS color stringInherited text colorParticle ink, including alpha. Theme changes redraw a paused field.
densitynumber1Visible fraction, clamped to 0–1. Does not change FFT resolution.
speednumber1Ambient time multiplier, clamped to 0–4. Zero freezes time.
interactionbooleantrueEnable mouse attraction; touch is ignored.
strengthnumber1Attraction multiplier, clamped to 0–2.
radiusnumber1.65Attraction radius in aspect-correct normalized screen units, clamped to 0.1–3.
pausedbooleanfalseFreeze ambient motion; mouse attraction can still respond.
respectReducedMotionbooleantrueShow a still and disable attraction under reduced motion. Override only after explicit opt-in.
fallbackReactNodeEmptyDecorative content shown while loading or when WebGPU cannot render.
children, className, style, other div propsNative div props—Foreground content, sizing and container styling.

Rendering and lifecycle

WebGPU requires a supporting browser and secure context (HTTPS or localhost). The renderer loads on the client. Unsupported hardware, initialization errors or device loss reveal the fallback; foreground content stays available. Remount to retry after device loss. Reduced motion gets a still frame by default.

The ocean uses the MIT-licensed vgpu FFT ocean example at 512² particles, with independently authored pointer interaction and presentation. Other presets use 16384 particles and skip the FFT and bloom pipeline. Custom point clouds use their supplied count. Density hides a stable subset; it does not reduce ocean simulation cost. The frame loop caps at 30 FPS and 1.6 DPR, pauses offscreen and in hidden tabs, and skips GPU submissions when a paused field settles. Multiple ocean instances are expensive; prefer one shared background. Desktop verification does not establish phone performance.

Shape changes and resizes rebuild GPU resources while preserving the animation phase. Unmount releases listeners, observers, frames and the GPU device. The ocean has a softer opacity and bottom fade; compact presets are unmasked. Keep LICENSE.vgpu.txt when reusing or distributing these files. The source example revision is recorded in PROVENANCE.md in this repository.

Inspired by https://www.agent-native.com/ — steal-ui entry agent-native-hero-particles.