MynduiMyndui
0

Anatomy of Flow Field

How value noise becomes a vector field, how a translucent wipe leaves silk trails, and how observers keep the rAF loop cheap when idle.

01/04Noise → angle → step
Noise
valueNoise(x·scale, y·scale+z)
Angle
noise × π × 4
Step
1.6 × speed px / frame
tsx
const angle =valueNoise(p.x * noiseScale, p.y * noiseScale + z) * Math.PI * 4;p.x += Math.cos(angle) * 1.6 * speed;p.y += Math.sin(angle) * 1.6 * speed;z += 0.0008 * speed; // field evolves slowly
01

Noise → angle → step

FlowField paints particles on a canvas that stream along an evolving noise field. There is no SVG path and no retained trail geometry — each frame samples noise for an angle, steps the particle, then fades the whole buffer a hair so yesterday's stroke lingers as silk.

Per particle, every frame:

noiseScale defaults to 0.0016 — smaller means broader, smoother currents. speed scales both the step and the z drift.

Particle count defaults to 900, then clamps so dense viewports don't melt the main thread:

tsx
 
Math.max(120, Math.min(particleCount, Math.round((w * h) / 1400)))
02/04Fade wipe trails

ctx.fillStyle = rgba(bg, fade) · default fade 0.06

Wipe
full-canvas translucent rect each frame
Silk
lower fade → longer trails
tsx
ctx.fillStyle = `rgba(${br}, ${bg}, ${bb}, ${fade})`; // default fade = 0.06ctx.fillRect(0, 0, w, h);// then stroke each particle's latest segment in --primary
02

Fade wipe trails

Instead of storing polylines, every frame fills the canvas with a translucent rect in the background color:

Lower fade → longer, silkier wakes. Higher → crisp, short ticks. Theme colors re-resolve via a MutationObserver on <html> (class / data-theme / style) so light and dark both stay correct.

03/04Cheap when offscreen
offscreen → paused
tab hidden → raf = 0
240 steps → freeze
Intersection
IO pauses the loop once the canvas leaves the viewport
Visibility
document.hidden stops frames while the tab is backgrounded
Reduced
~240 frozen steps, then a still frame — no ongoing rAF
03

Cheap when offscreen

Three gates stop the loop when work would be wasted:

GateBehavior
IntersectionObserverpause when the canvas leaves the viewport
visibilitychangepause when document.hidden
prefers-reduced-motionrun ~240 frozen steps, then stop on a still frame

ResizeObserver rebuilds the buffer when the container changes size.

04/04Result
04

The result

Sample → step → wipe. A living field that refuses to burn frames when nobody is looking.

Motion Score

Flow FieldCCPaint-triggering
Ccanvas paint2D canvas particle trails each frame
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →