jb-website/reference/particle-field-demo/README.md
Josh Bairstow f4b8e1a7d8
All checks were successful
deploy / build-and-deploy (push) Successful in 31s
particle field planning
2026-07-23 23:14:04 +10:00

4.7 KiB
Raw Permalink Blame History

particle-network

A canvas animation of drifting particles connected by proximity lines, with opacity modulated by a slowly-rolling fBm (fractal Brownian motion) noise field.

No external dependencies. Pure ES module.


Files

File Purpose
particleNetwork.js The module — import this into your project
demo.html Minimal working example (requires a local server for ES module import)

Quick start

Serve the folder with any static server, e.g.:

npx serve .
# or
python3 -m http.server

Then open demo.html.


Integration

1. Minimal — full-page background

<canvas id="net"></canvas>

<script type="module">
  import { ParticleNetwork } from './particleNetwork.js';

  const canvas = document.getElementById('net');

  // Size the canvas to the viewport yourself, or let the module
  // read the parent element's bounding rect (default behaviour).
  canvas.style.display = 'block';
  canvas.style.width   = '100%';

  const net = new ParticleNetwork(canvas, null);
  net.start();
</script>

2. With field preview (debug / design mode)

<canvas id="net"></canvas>
<canvas id="field"></canvas>

<script type="module">
  import { ParticleNetwork } from './particleNetwork.js';

  const net = new ParticleNetwork(
    document.getElementById('net'),
    document.getElementById('field'),
    { count: 60, dimAtMin: 0.5 }
  );

  net.start();
</script>

3. Bundler / framework

Copy particleNetwork.js anywhere in your src/ tree and import normally:

import { ParticleNetwork } from '@/lib/particleNetwork.js';

No build config changes needed — it uses only standard browser APIs.


Constructor

new ParticleNetwork(mainCanvas, fieldCanvas, options)
Parameter Type Description
mainCanvas HTMLCanvasElement The particle animation canvas
fieldCanvas HTMLCanvasElement | null Optional debug field view
options object See below

Options

All options can be passed at construction time and updated at runtime via setOption(key, value).

Key Default Description
count 55 Number of particles
reach 140 Max connection distance in canvas px
flowSpeed 2 Field drift speed on a 110 scale. At 1, motion takes several minutes to traverse the field — clearly present but takes time to perceive.
fieldScale 4 Noise zoom on a 110 scale. Lower = larger rolling blobs.
dimAtMin 0.60 Opacity reduction at field minimum. 0 = field has no opacity effect. 1 = particles in troughs are fully invisible.
particleSpeed 0.35 Base particle velocity (px/frame)
particleMinR 1.4 Minimum particle radius (px)
particleMaxR 2.8 Maximum particle radius (px)
fieldUpdateMs 50 How often the noise field recalculates (ms). Lower = smoother but more CPU.
fieldOctaves 4 fBm octaves. More = finer detail, more CPU.
bgColor '#040e1f' Canvas background colour
lineColor '80,160,240' RGB string for particleparticle lines
lineColorMouse '160,220,255' RGB string for mouse-proximity lines
canvasAspect 0.65 Canvas height = width × aspect
timeConstantX 0.00084 Primary axis time advance rate. Halve to slow further.
timeConstantY 0.0003145 Secondary axis rate — kept at a different ratio for the swirling character.

Methods

net.start()               // begin animation loop
net.stop()                // pause (preserves state)
net.setOption(key, value) // update any option at runtime
net.sampleField(x, y)    // returns [0,1] field value at canvas coords
net.destroy()             // stop loop and remove all event listeners

How it works

Noise field — a 4-octave fBm Perlin noise function is evaluated on a tiny 80×52 offscreen canvas every 50ms, then scaled up via drawImage. This is ~100× cheaper than evaluating at display resolution.

Opacity mapping — each particle's field sample fv ∈ [0,1] maps to an opacity multiplier via:

multiplier = (1 - dimAtMin) + dimAtMin * fv

At fv = 1 (field peak): multiplier = 1.0 — full brightness. At fv = 0 (field trough): multiplier = 1 - dimAtMin.

For connecting lines the multiplier is the geometric mean of both endpoint values, so a line straddling a bright and dark zone stays dim.

Distance falloff — cubic: (1 - dist/reach)³, giving a sharp fade near the connection threshold rather than a linear fade across the full range.

Glow — each particle renders a createRadialGradient halo sized to r × (2.5 + fieldValue × 1.5), so dots in bright field regions bloom larger.