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

161 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.:
```bash
npx serve .
# or
python3 -m http.server
```
Then open `demo.html`.
---
## Integration
### 1. Minimal — full-page background
```html
<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)
```html
<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:
```js
import { ParticleNetwork } from '@/lib/particleNetwork.js';
```
No build config changes needed — it uses only standard browser APIs.
---
## Constructor
```js
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
```js
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.