All checks were successful
deploy / build-and-deploy (push) Successful in 31s
161 lines
4.7 KiB
Markdown
161 lines
4.7 KiB
Markdown
# 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 1–10 scale. At 1, motion takes several minutes to traverse the field — clearly present but takes time to perceive. |
|
||
| `fieldScale` | `4` | Noise zoom on a 1–10 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 particle–particle 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.
|