GodUIGodUI
129Follow on X

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. It keeps no SVG path and no trail geometry. Each frame samples noise for an angle, steps the particle, then fades the whole buffer slightly so earlier strokes linger as trails.

Per particle, every frame:

noiseScale defaults to 0.0016. Smaller values give 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 gives longer, silkier wakes. Higher gives 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 with 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. The loop stops drawing frames when nobody is looking.

Motion Score

Flow FieldCC: Paint-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 →