Study 02 / Ink Current
The current is designed by its eraser
Ink Current stores no stroke. A bounded tracer population draws one segment per frame; velocity damping and a time-corrected framebuffer wash decide how long the gesture remains visible.
Which should disappear first: the motion or its visible trace?
Open the live system- Image memory
- ~130ms half-life
- State memory
- ~408ms half-life
On this page
01 / Ink Current / The vanishing mark
The line is never stored.
Ink Current does not record a stroke. There is no path buffer and no stream of particles emitted beneath the pointer. The scene begins with an existing population, remembers each particle’s previous position for one frame, and draws only the short segment from that position to the next.
The longer mark is assembled by the framebuffer. New segments are added with lighter compositing while a dark wash removes part of the previous image. What looks like a continuous line is accumulated light: a sequence of temporary observations, not stored geometry.
That distinction defines the pattern’s limits. It can make movement legible, but it cannot reproduce a signature, edit a drawn path, or tell the visitor exactly where a gesture travelled.
- 01Sample
One time-normalized pointer displacement
- 02Couple
A 210px field with linear falloff
- 03Advance
One previous-to-current segment
- 04Erase
A delta-corrected framebuffer wash
The trail is the time it takes a frame to forget.
02 / Bounded population
Density is authored before motion begins.
The tracer count follows the Canvas area. Small viewports keep a floor of 240 particles; large ones stop at 520. The ceiling bounds per-frame work, while the floor prevents the current from becoming visually empty on mobile. Every non-wrapping particle contributes at most one line segment per frame, so update and drawing work remain linear with the population.
round(clamp(width × height ÷ 3100, 240, 520))The default seed 1108 assigns initial positions, phase offsets, and sizes from 0.7px to 2px. Cyan, violet, and pink cycle by array index. This makes the initial distribution repeatable for a given viewport, but it does not encode data or particle identity. The population is art-directed texture, not a visualization.
Resize rebuilds that population. The scene is deterministic at initialization, not immutable over time: viewport dimensions, scene-local phase, and pointer input still determine the live result. The catalog count used by Sparks has no effect here.
03 / Direction from phase
Direction comes from phase, not fluid.
Each tracer receives an angle from two slow trigonometric waves. The x term spans roughly 1,047 CSS pixels per cycle and changes over about 28.6 seconds; the y term spans roughly 785 pixels and changes over about 37 seconds. A per-particle seed shifts the first phase, so two tracers at the same coordinate do not necessarily receive the same direction.
theta = 1.55 sin(0.006x + 0.00022t + seed)
+ 1.15 cos(0.008y - 0.00017t)
velocity += 24 * delta * (cos(theta), sin(theta))
position += 60 * delta * (velocity + 0.44 * direction)The implementation converts the angle into a unit direction with cosine and sine, adds directional acceleration, and advances position against a 60Hz reference. This keeps the composition’s approximate pace stable across common refresh rates without pretending the constants are physical units.
It is an analytic steering rule, not a fluid simulation. There is no pressure, density, incompressibility constraint, neighbor exchange, noise texture, or grid solver. The model borrows the visual language of a current because curved routes are useful here; it does not claim to predict one.
04 / Pointer injection
The pointer contributes a pulse, not a stroke.
Movement is converted into a 60Hz-reference displacement sample. Each axis is normalized by the time between pointer events, then capped at ±28px. The first move after the pointer enters the active field contributes no travel velocity. Later samples receive a linear envelope that falls from one to zero over 120ms, so the final event cannot drive the scene forever.
u = clamp(
displacement * ((1000 / 60) / elapsedMs),
-28,
28
)Only tracers within 210px respond. A linear falloff reaches zero at that boundary. Hover applies a direct gain of 0.34; pressing raises it to 0.90. A second, tangential term uses the perpendicular vector (−dy, dx) to bend local motion, with gains of 0.6 for hover and 1.5 while pressed. Press changes strength, not the direction of the model. At the baseline intensity those gains are unscaled; the controller bounds their shared multiplier between 0.4 and 1.6.
- Radius
- 210px
- Direct gain
- 0.34 / 0.90
- Tangent gain
- 0.60 / 1.50
Links, controls, and the reading surface are excluded from this field. Pointer feedback belongs to open space; selecting text or activating navigation should not inject motion into the artwork. Touch has no hover phase, and vertical movement remains available to page scrolling through touch-action: pan-y.
05 / Two decay clocks
State and image forget at different rates.
Particle velocity is multiplied by e^(−1.7 × delta). In the absence of the continuing direction rule and pointer force, that gives added velocity a half-life of about 408ms. The overall field never settles because procedural steering continues; the number describes gesture memory above that ambient movement.
The visible trace uses a shorter clock. At the 60Hz reference, each frame applies a dark wash with alpha 0.085. For an arbitrary frame delta, the equivalent clear alpha is derived from the same retention value. This preserves an optical half-life of about 130ms instead of letting a 120Hz screen erase the image twice as fast as a 60Hz screen. The displayed 0.085 value is intensity 50; the bounded controller moves reference wash alpha from 0.14 toward 0.03 as energy rises, so higher energy also leaves a longer trace.
Framebuffer retention derived from 0.915 at the 60Hz reference.
Exponential damping in the absence of continuing field or pointer force.
alpha(delta) = 1 - (1 - 0.085)^(60 * min(delta, 0.032))The Ink integrator caps its local delta at 32ms, inside the shared loop’s 34ms cap. Position and direct input use the same reference frame scale as the framebuffer wash. Discarding excess elapsed time prevents one oversized step after a stall, although the visible scene will briefly run behind wall-clock time.
06 / Boundaries and stillness
Edges wrap; reduced motion changes the drawing.
Tracers travel slightly beyond the visible field before wrapping at −12px andwidth + 12px, or the corresponding vertical bounds. A wrap does not draw a segment. The previous position is reset to the destination and that frame is skipped, preventing a false line from cutting across the entire Canvas.
Work remains bounded but not free. Each active frame evaluates trigonometric functions, updates and strokes 240–520 tracers, and washes the full backing store. The loop is O(n), unlike Sparks’ pair scan, while a DPR ceiling of 2 prevents backing-store area from growing without limit. A material resize rebuilds transient scene runtimes from their current seeds, but the Garden field remains lazy and controller settings are retained.
The animated system stops when the document is hidden or the landing leaves the viewport. With prefers-reduced-motion: reduce, requestAnimationFrame never starts. Ink renders a separate still composition instead: 34 sine paths distributed through the central field, sampled every 12px, with amplitudes from 28px to 64px and alternating cyan and violet strokes. Its phase reflects the current seed without changing live tracers.
That still is not a frozen simulation frame. It is an authored substitute that communicates layered direction without continuous tracking. The Canvas remainsaria-hidden; explanation, instructions, and destinations live in ordinary HTML. The visible Pause / Resume transport now sits outside that hidden subtree. User pause preserves the current framebuffer and tracer state, while the reduced-motion preference continues to select this separate still composition.
07 / Where residue belongs
Use residue only when residue has a job.
Ink Current suits an ambient editorial field, a short transition, or nonessential feedback where seeing movement persist briefly clarifies that an input had direction and consequence. It works best behind stable content, inside a bounded region, and with a conventional interface carrying every important action and label.
- 01Useful feedback
A short trace confirms direction, then clears before the next task begins.
- 02Lingering decoration
The mark remains after its meaning has passed and starts competing with copy.
- 03Noise
Residue is mistaken for durable state, exact geometry, or a selectable route.
It is the wrong model for signatures, handwriting, route capture, or any tool that must reproduce a gesture: no path is stored. It should also stay away from dense forms, precise comparison tasks, and status communication, where luminous residue can obscure information or be mistaken for state.
The test is simple. If a fading trace helps explain that input had direction and consequence, the effect has a reason to exist. If the visitor must inspect the trace to understand what happened, use explicit geometry, durable state, and semantic feedback instead.
Method
References and implementation sources
This article documents the live implementation running on this site. These primary standards and publication records were used to verify API behavior and technical terminology.
- HTML Canvas 2D ContextWHATWG HTML Living Standard
Path drawing, alpha compositing, and state used by the tracer and framebuffer wash.
- Pointer Events Level 3W3C
Unified pointer input and capture semantics used for injection and cancellation.