Documentation

Dotfield

Zero-dependency living stipple particle library. Install as an npm package, pick from thousands of named modes, and customize palette, density, speed, and motion.

Package dotfield · current API v1.1.0

Introduction

Dotfield draws a soft, film-like field of dots on a <canvas>. Particles ride noise flow fields and geometric attractors. You select a mode id from a large catalog, optionally override appearance and intensity, and control the field with a small public API.

  • Browser: Dotfield.create(canvas, options)
  • Headless / tests: Dotfield.createSimulation(options)
  • Catalog: Dotfield.listModes() / getMode(id)

The demo site uses two independent fields: a fixed full-page backdrop and a large gallery preview that switches modes when you pick a card. Your app typically needs only one field.

Installation

Install Dotfield as a dependency:

npm install dotfield

Package page: npmjs.com/package/dotfield. Entry is main / exportslib/dotfield.js.

CommonJS

const Dotfield = require('dotfield');

ES modules / bundlers

import Dotfield from 'dotfield';
// or
import * as Dotfield from 'dotfield';

Plain script tag (no bundler)

Point a script at the package main file (after install, typically node_modules/dotfield/lib/dotfield.js). The library attaches window.Dotfield.

<script src="node_modules/dotfield/lib/dotfield.js"></script>
<script>
  const field = Dotfield.create(document.getElementById('dots'), {
    mode: 'flow-calm-cw-mid',
    fillParent: true,
  });
</script>

Quick start

<canvas id="dots"></canvas>
<script src="node_modules/dotfield/lib/dotfield.js"></script>
<script>
  const field = Dotfield.create(document.getElementById('dots'), {
    mode: 'orbit-steady-cw-mid',
    fillParent: true,
    palette: 'terracotta',
    background: 'cream',
    speed: 1,
    intensity: 1.1,
  });

  // Later…
  field.setMode('vortex-brisk-ccw-fine');
  field.setConfig({ palette: 'ocean', density: 1.2 });
  // field.stop(); field.destroy();
</script>

Headless (Node tests, SSR-safe stepping without a real canvas):

const Dotfield = require('dotfield');

const sim = Dotfield.createSimulation({
  width: 400,
  height: 300,
  count: 48,
  seed: 42,
  mode: 'wave-calm-cw-mid',
  speed: 1.2,
});

for (let i = 0; i < 60; i++) sim.step(1 / 60);
const positions = sim.getPositions();

API reference

The public surface is intentionally small. Everything below is stable for application code. Underscore-prefixed properties (e.g. _catalogLength) are internal.

Dotfield.create(canvas, options?)

Creates a live animated field on a canvas element. Returns a field instance. Throws if canvas is missing or not a canvas.

OptionTypeDefaultDescription
modestringflow-calm-cw-midCatalog mode id
fillParentbooleancontextualtrue sizes to window; false sizes to parent/canvas box
wrapbooleanfalseLoop particles from each edge to the opposite edge instead of bouncing
wrapInsetnumber0Off-screen gutter used before wrapping to the opposite side; edge-tangent particles are gently carried through instead of forming border lanes
width / heightnumberFixed logical size when set
countnumberautoFixed particle count
maxParticles / minParticlesnumber5200 / 1200Auto-density clamps
densitynumber1Relative density when count is auto
autoStartbooleantrueBegin RAF loop immediately
interactivebooleantrueMaster cursor on/off — set false to disable all pointer forces
mouseModestring'repel'repel · attract · swirl · off
mouseForce / mouseRadiusnumbermode-tunedStrength & pixel radius of cursor influence
autoMousebooleantrueRe-tune radius/force on mode change & resize
mouseSmoothnumber0.12Pointer follow ease (0–1; higher = snappier)
seednumberrandomParticle spawn seed
pausedbooleanfalseStart paused (simulation still can draw)
palette / colorssee Configmode defaultDot colors
background / bgsee Configmode defaultTrail clear color
speed, speedScale, intensity, …numbermodeMotion overrides (see Customization)

Dotfield.createSimulation(options?)

Pure simulation without canvas I/O. Ideal for unit tests and deterministic stepping. Shares the same motion math as create.

const sim = Dotfield.createSimulation({
  width: 320,
  height: 240,
  count: 32,
  seed: 1,
  mode: 'spiral-rush-cw-vast',
  palette: 'ember',
});
sim.step(1 / 60);
sim.setMode('grid-calm-ccw-fine');
sim.setConfig({ speed: 2 });
const snap = sim.getSnapshot();

listModes() / getMode(id)

Dotfield.listModes() returns an array of:

{
  id: string,           // stable, e.g. "orbit-brisk-ccw-mid"
  label: string,        // human-readable
  family: string,       // e.g. "orbit"
  familyLabel: string,
  description: string
}

Dotfield.getMode(id) returns the same plus params (motion parameters), or null if unknown.

Also available: Dotfield.families, Dotfield.palettes, Dotfield.backgrounds, Dotfield.version.

Instance methods

MethodDescription
setMode(id)Switch catalog mode; keeps consumer overrides
getMode()Current mode id string
getModeInfo()Full mode metadata for current id
setConfig(opts)Merge customization overrides (see below)
getConfig()Mode, overrides, resolved palette/bg, count
listModes()Same as Dotfield.listModes()
pulse()Radial burst impulse
getPositions()Array of {x,y,vx,vy}
getParticleCount() / getFps()Diagnostics (canvas field)
retuneMouse()Force recompute of mode-tuned radius/force for current size
getMouseSettings(){ mouseRadius, mouseForce, mouseMode, interactive, autoMouse, mouseSmooth }
resize()Recompute canvas size & density
step(dt?)Advance one tick (also on simulation)

setConfig(options)

Merges overrides onto the current mode. Overrides persist across subsequent setMode calls until you change them again.

field.setConfig({
  mode: 'bloom-steady-cw-mid', // optional: also switches mode
  palette: 'ocean',            // or 'rainbow'
  background: '#0c0c0e',
  speedScale: 1.4,
  intensity: 1.2,
  density: 1.3,
  // or count: 2000,
  trail: false,                // disable stain / motion smear
  interactive: true,           // false = fully disable cursor
  mouseMode: 'swirl',
  mouseForce: 2,
  mouseRadius: 160,
  mouseSmooth: 0.2,
  autoMouse: false,            // lock manual force/radius
});

Lifecycle

MethodDescription
start()Start animation loop (no-op if already running)
stop()Stop RAF / timeout loop
destroy()Stop, unbind listeners, clear particles
pause() / resume() / togglePause()Programmatic pause (no site UI button)
isRunning() / isPaused()State flags
isAutoPaused()Whether visibility/intersection safeguards currently suppress simulation

Customization

Pass options at create time or via setConfig. Named palettes and backgrounds ship with the library; you can also pass raw RGB or hex.

Colors

  • Named palettes: terracotta, ink, ocean, dusk, forest, ember, frost, gold, rose
  • Rainbow: palette: 'rainbow' or rainbow: true — particles cycle through hues continuously (animated)
  • Custom: [{ r, g, b }, …], ['#c45c3e', …], or a single hex string
  • Backgrounds: cream, paper, night, slate, mist, sand, ink, or hex / {r,g,b}

Motion & density

KeyEffect
speedAbsolute speed multiplier for the mode
speedScaleMultiplies existing speed
intensityScales noise amplitude and attractor pull
scaleNoise spatial scale
noiseAmpFlow noise strength
spin1 / -1 direction bias
pull / morphBiasAttractor strength
rings / clustersOrbit / constellation structure
waveFreq / waveAmpWave / ribbon parameters
countExact particle count (respawns)
densityRelative density when count is automatic
quality'high' | 'medium' | 'low' — DPR, glow, particle defaults
adaptiveQualityReduces draw work when measured FPS drops; simulation remains deterministic
autoPausePauses animation work while the document is hidden or the canvas is offscreen
glowSoft radial glow highlights (costly on large fields)
interactiveEnable pointer / mouse forces on the canvas

Cursor interaction

Cursor behaviour is fully optional. When enabled, the field reacts to the pointer using canvas-local coordinates (embedded previews and full-bleed heroes). You can turn it off entirely, pick a force mode, lock or auto-tune radius/force, and control how snappy the follow feels.

Disable cursor interaction

Three equivalent ways to turn cursor forces off:

  1. Master switch: interactive: false — no pointer tracking, no forces. Prefer this for pure decorative fields or reduced-motion-friendly pages.
  2. Mode off: mouseMode: 'off' (alias 'none') — keeps listeners but applies zero force.
  3. Zero force: mouseForce: 0 — same effect as mode off for force application.
// Decorative only — no cursor reaction
Dotfield.create(canvas, {
  mode: 'serene-float-whisper-cw-soft',
  fillParent: true,
  interactive: false,
});

// Runtime toggle (e.g. accessibility control)
field.setConfig({ interactive: false });
field.setConfig({ interactive: true, mouseMode: 'repel' });

All cursor options

KeyDefaultEffect
interactivetrueMaster on/off. false disables pointer forces completely
mouseMode'repel'repel · attract · swirl (alias vortex) · off / none
mouseForcemode-tunedStrength of the cursor influence. 0 = no push
mouseRadiusmode-tunedPixel radius of influence — auto-scales to mode + canvas size
autoMousetrueWhen true, radius/force re-tune on setMode / resize
mouseSmooth0.12Pointer follow ease (0–1). Low = soft lag, high = snappy, 1 = instant

Radius and force are derived from the mode’s engine, speed, calm flag, and canvas size (e.g. open flow/drift fields get a wider hand; dense grid modes get a tighter one; calm modes get a gentler, smaller radius). Setting mouseRadius or mouseForce via setConfig locks auto-tune until the next setMode or setConfig({ autoMouse: true }).

// Interactive swirl with manual lock
Dotfield.create(canvas, {
  mode: 'orbit-steady-cw-mid',
  fillParent: true,
  interactive: true,
  mouseMode: 'swirl',
  mouseForce: 2.2,
  mouseRadius: 180,
  mouseSmooth: 0.2,
  autoMouse: false, // keep fixed force/radius across modes
});

// Auto-fit radius to each mode
field.setConfig({ autoMouse: true });
field.setMode('serene-float-whisper-cw-soft'); // retunes radius
field.retuneMouse(); // force recompute for current size
field.getMouseSettings();
// → { mouseRadius, mouseForce, mouseMode, interactive, autoMouse, mouseSmooth }

// Soft attract with snappy pointer
field.setConfig({
  mouseMode: 'attract',
  mouseSmooth: 0.45,
  mouseRadius: 220,
  mouseForce: 1.2,
});

The gallery preview’s Show controls panel exposes the same options: master On/Off, mode (Off · Repel · Attract · Swirl), Force, Radius, Smooth, and Auto cursor. The embed snippet updates when you change them.

Trail (stain) & rainbow

By default the renderer clears each frame with a translucent background fill. That creates the soft trail / stain left when particles cross an area. Set trail: false for a clean solid wipe every frame (no smear).

KeyDefaultEffect
trailtrueSoft motion smear. false = solid clear, no stain
rainbowfalseAnimated multi-hue particles (same as palette: 'rainbow')
palette: 'rainbow'Alias for animated rainbow colouring
// Clean field, multicolour particles
Dotfield.create(canvas, {
  mode: 'flow-calm-cw-mid',
  fillParent: true,
  trail: false,      // no stain / smear
  rainbow: true,     // or palette: 'rainbow'
  interactive: true,
  mouseMode: 'repel',
});

field.setConfig({ trail: true });   // re-enable soft paths
field.setConfig({ rainbow: false, palette: 'ocean' });
// Dark product page accent
Dotfield.create(canvas, {
  mode: 'constellation-calm-cw-mid',
  fillParent: true,
  palette: [
    { r: 120, g: 200, b: 255 },
    { r: 80, g: 140, b: 220 },
  ],
  background: 'night',
  intensity: 1.35,
  count: 3200,
  trail: true,
});

Modes & catalog

Modes are parametric combinations of families (motion engines), speed, spin, and noise scale, plus tint variants and a large serene / calm series tuned to be smooth and easy on the eyes. Each mode has a stable string id safe to hard-code in apps.

const modes = Dotfield.listModes();
console.log(modes.length); // thousands

// Calm-only catalog (whisper / gentle / still / calm + serene-*)
const calm = Dotfield.listCalmModes();
// or: modes.filter((m) => m.calm || m.serene || Dotfield.isCalmMode(m.id));

// Filter by family
const orbits = modes.filter((m) => m.family === 'orbit');

// Resolve metadata
const info = Dotfield.getMode('serene-float-whisper-cw-soft');
// info.params.engine, info.params.speed, info.calm, info.serene …

Calm & serene modes

Speeds whisper, gentle, still, and calm are marked calm. Dedicated families (float, glide, hush, murmur, lull, breath, meadow, glass, zen, cloud, velvet, silk, mistflow, lagoon, emberglow, moonring, softgrid, driftwood) and every serene-* id use lower noise, softer pull, higher damping, and slower field evolution so motion stays smooth.

// Easy-on-the-eyes hero
Dotfield.create(canvas, {
  mode: 'serene-zen-gentle-cw-ease',
  fillParent: true,
  trail: false,
  interactive: true,
});

Families (examples)

Core engines include flow, orbit, constellation, wave, spiral, vortex, grid, ribbon, bloom, rain, figure8, helix, scatter, pulse, drift, magnet, weave, halo, cascade, swarm. Extended family names (aurora, tide, lattice, comet, fog, mill, braid, echo, nexus, gyre, petal, cord, lance, stream, infinity, poles) map onto those engines with distinct parameter biases so motion still differs.

Id shape

// family-speed-spin-scale
'flow-calm-cw-mid'
'orbit-brisk-ccw-fine'
'vortex-rush-cw-vast'

// tint variants
'orbit-tint-ocean-tight'

// short legacy aliases (family-speed-spin) also resolve
'flow-calm-cw'

Browse every mode on the live gallery — the large preview updates without changing the page backdrop.

Examples

Hero background

const field = Dotfield.create(document.getElementById('hero-canvas'), {
  mode: 'flow-calm-cw-mid',
  fillParent: true,
  interactive: true,
  maxParticles: 4000,
  palette: 'terracotta',
  background: 'cream',
});

Mode picker in your UI

const select = document.getElementById('mode-select');
Dotfield.listModes()
  .filter((m) => m.family === 'orbit')
  .slice(0, 40)
  .forEach((m) => {
    const opt = document.createElement('option');
    opt.value = m.id;
    opt.textContent = m.label;
    select.appendChild(opt);
  });

select.addEventListener('change', () => {
  field.setMode(select.value);
});

Package consumer (Node)

See examples/package-consumer/ in the repo — it installs via file: and require('dotfield') (Node sample, not a hosted web page).

const Dotfield = require('dotfield');
const sim = Dotfield.createSimulation({
  seed: 42,
  count: 24,
  mode: 'orbit-steady-cw-mid',
  palette: 'ocean',
  background: 'night',
});
for (let i = 0; i < 20; i++) sim.step(1 / 60);
console.log(sim.getPositions().length);

Minimal HTML

Live: examples/minimal.html · source on GitHub.

Testing

npm test                 # catalog size, motion, customization
npm run test:gallery     # dual-field isolation + markup
npm run test:consumer    # file: package require path
npm run test:browser     # Playwright probe (optional)

Prefer createSimulation in unit tests: fixed seed, equal step counts, compare positions. Assert customization by comparing snapshots after different speed / palette / count options.

FAQ

Does Dotfield need a build step?

No. The shipped file is plain IIFE + CommonJS-friendly. Bundlers optional.

Can I use it with React / Vue?

Yes — create the field in useEffect / onMounted with a canvas ref, and call destroy() on unmount. No official wrappers ship in this package.

How do I keep performance high?

Cap maxParticles or set a fixed count. Preview surfaces can use fewer particles than full-bleed heroes. Prefer one field per page when possible.

Is pause available?

Yes programmatically via field.pause() / resume() / togglePause(). The demo site does not expose a Pause button.

How do I disable cursor / mouse interaction?

Pass interactive: false at create time, or call field.setConfig({ interactive: false }) at runtime. Alternatives: mouseMode: 'off' or mouseForce: 0. See Cursor interaction → Disable.

Where is the source of truth for modes?

Generated inside lib/dotfield.js at load time. Always query listModes() rather than hard-coding the count.