Study 05 / Gesture-to-Easing

The route survives. The timing is rewritten.

The field accepts movement beyond four pixels, keeps the latest 160 points, and caches cumulative distance while retaining timestamps that the comparison does not replay. The default 4.3-second clock maps linear, cubic ease-out, and clamped damped-cosine timing over one polyline.

What can easing express after the original gesture timing is deliberately discarded?

Open the live system
Gesture-to-Easing choreographic distance scoreThe actual cached default route is drawn with distance ticks. At base progress point three five, a cyan circle marks linear distance, a violet diamond marks cubic ease-out, and a rose triangle marks the clamped damped-cosine result. A separate timestamp rail is labeled stored, not read.CHOREOGRAPHIC DISTANCE SCORE / CS‑05BASE P / 0.35
Linear
0.350
Cubic ease-out
0.725
Damped cosine
1.115
The route is the actual 72-point cached helper path. At p=0.35, all three positions use cumulative distance; the spring scalar exceeds one and therefore resolves to the clamped endpoint.
Published
Jul 12, 2026
Updated
Jul 12, 2026
Reading
11 min
On this page
  1. Route and timing
  2. Capture gate and buffer
  3. Arc-length parameter
  4. Stored versus consumed
  5. Three progress maps
  6. Lifecycle, trail, and stillness
  7. Choose by intent

01 / Gesture-to-Easing / Route and timing

A route and its timing are different records.

Gesture-to-Easing keeps the coordinates of a pointer route, then gives that route a new clock. Linear, cubic ease-out, and damped-cosine progress all visit the same cumulative distances. None reconstructs the cadence of the hand that supplied the points.

That separation is the subject of the Study. Geometry answers where; a timing model answers when. A convincing replay needs an explicit policy for both. Calling an easing curve natural cannot replace the missing decision.

The current engine is therefore an easing visualizer, not a faithful gesture recorder. It is useful precisely when that boundary remains visible: one route becomes a score on which three authored clocks can be compared.

02 / Capture gate and buffer

Four pixels decide what enters the score.

A primary pointer press in open Canvas space starts a new path with one point. That single point is already a valid custom route; its three markers share one position until another sample arrives. Mouse input must use the main button, and links, controls, and the reading surface never begin capture.

The valid press also asks the Canvas for DOM Pointer Capture, so sampling can continue across descendant boundaries. Pointer up, cancel, lost capture, browser blur, scene exit, and movement beyond the active field all close both the DOM capture and the drawing session. If Pointer Capture is unavailable, a zero-button move or a pointer leaving the browser window provides the fallback release signal.

Distance-gated pointer samplesA raw pointer trace contains small rejected events between larger accepted sample marks. Each accepted mark must be more than four pixels from the last accepted point.> 4 PX / ACCEPTCROSSES / POINTER EVENTS NOT ADMITTED
The gate is spatial but the opportunities are event-driven. It reduces near-duplicate points without claiming uniform time sampling.

During drawing, a new point is admitted only when its Euclidean distance from the latest accepted point is greater than 4px. The gate reduces nearly identical samples, but it does not make sampling uniform: pointer-event cadence and jump size can still vary.

The capture gate and ceiling are intentionally small.
const distance = hypot(point.x - latest.x, point.y - latest.y);
if (distance <= 4) return false;

path.points.push({ x: point.x, y: point.y, time: point.time });
if (path.points.length > 160) {
  path.points.shift();
  rebuildGestureMetrics(path);
}
The path is a sliding window of the latest samples

No decimator protects the original start point. Once sample 161 arrives, the route becomes a suffix of the gesture.

The ceiling bounds live state at the latest 160 samples. It does not preserve the original endpoint or simplify the whole gesture: shift() removes the oldest point, then cumulative distance is rebuilt for the remaining suffix. The coordinates are still unquantized JavaScript numbers, so this memory bound is not yet a share-URL byte budget.

03 / Arc-length parameter

Equal progress now means equal geometric travel.

Every accepted segment contributes its Euclidean length to a cumulative array. To find a point at normalized progress p, the helper multiplies p by total path length, binary searches the first cumulative value at or beyond that target, then interpolates inside the selected segment.

Arc-length interpolation across unequal sample segmentsThree samples form a nine-pixel segment followed by a one-pixel segment. Half progress lands five pixels from the start, independent of sample timestamps.
Cumulative distance gives equal geometric travel to equal progress. It deliberately does not reproduce the recorded cadence.
Lookup is based on distance, not array position.
const targetLength = clamp(progress) * totalLength;
let low = 1;
let high = cumulativeLengths.length - 1;
while (low < high) {
  const middle = Math.floor((low + high) / 2);
  if (cumulativeLengths[middle] < targetLength) low = middle + 1;
  else high = middle;
}

const index = low;
const segmentStart = cumulativeLengths[index - 1];
const segmentLength = cumulativeLengths[index] - segmentStart;
const local = segmentLength > Number.EPSILON
  ? (targetLength - segmentStart) / segmentLength
  : 0;

return {
  x: lerp(points[index - 1].x, points[index].x, local),
  y: lerp(points[index - 1].y, points[index].y, local)
};

This corrects a common comparison error. Dense event clusters no longer consume more of the default 4.3-second cycle merely because they contain more array entries. A long segment receives more time than a short segment under the linear map.

Arc length is still a policy, not recovered truth. It produces constant geometric speed for linear progress; it cannot reconstruct a pause, acceleration, or release that was recorded in timestamps. A zero-length or one-point path resolves safely to its first coordinate.

04 / Stored versus consumed

The timestamp remains in the record, outside the replay.

Each accepted point stores x, y, and a performance.now() timestamp. The default route also carries 16ms-spaced time values. Cumulative-length construction and every point lookup read x and y only. No replay calculation reads time.

Stored fields and consumed fields are not the same contract

Capture record

x
route + length
y
route + length
time
retained only

Replay reads

positions
yes
cumulative lengths
yes
timestamps
no

Storing a field is not the same as giving it product meaning. The timestamps could later support an original-cadence reference, velocity estimation, or release analysis, but the current three models deliberately replace them with a scene-local clock.

Raw points remain in browser memory and are not sent to analytics or included in a URL. Before sharing is added, coordinates and timing would need quantization, versioning, and a strict serialized-size ceiling. The current 160-point count alone does not satisfy that contract.

05 / Three progress maps

The spring overshoots as a number, not as route distance.

After release, a local clock advances from zero to one over 4,300ms. Linear returns that value unchanged. Cubic ease-out uses 1 − (1 − t)³, moving through most of the distance early before approaching the endpoint. The third map is the analytic damped cosine 1 − e⁻⁶ᵗ cos(10t).

Linear, cubic ease-out, and damped-cosine progress mapsLinear and cubic ease-out remain between zero and one. The raw damped-cosine curve rises above one, while a second solid rose line shows the progress clamped to the route endpoint.
  • Linear
  • Cubic ease-out
  • Damped cosine
  • Raw spring above 1
The spring formula overshoots analytically. The route lookup clamps that scalar before converting it to a distance.

At t=0.25, the three raw values are 0.25, 0.578, and approximately 1.179. The spring value is genuinely above one, but pointOnGesture clamps progress before multiplying by total length. The visible triangle reaches the endpoint, waits while the scalar stays above one, retreats slightly when it falls below one, and returns.

Timing changes distance progress; lookup owns the boundary.
const linear = t;
const easeOut = 1 - Math.pow(1 - t, 3);
const spring = 1 - Math.exp(-6 * t) * Math.cos(10 * t);

// spring may be > 1; pointOnGesture clamps before lookup
const marker = pointOnGesture(path, spring);

This is not a mass–spring simulation with configurable stiffness or damping. It is one closed-form timing curve. If a product needs visible travel beyond an endpoint, it also needs an extrapolation rule—for example, extending the final tangent—rather than silently asking a bounded path lookup to invent geometry.

06 / Lifecycle, trail, and stillness

Release starts the clock; inactivity freezes it.

While drawing, base progress stays at zero and a white point marks the latest accepted sample. Pointer release, loss of the active field, or pointer leave ends capture and resets replay elapsed time. The 4,300ms value is the intensity-50 baseline. The valid 0–100 range maps exponentially from 8.6 to 2.15 seconds, while the clock advances only when Gesture is the active, visible, animated scene. Hidden or offscreen work stops without advancing it. Visible elapsed time is not capped, so any selected duration remains refresh-rate independent.

A viewport resize ends an active draw, scales every accepted coordinate into the new field, and keeps a released route's replay elapsed time. Rotation therefore changes the drawing's aspect with the Canvas instead of silently replacing it with the default path.

Three lanes share distance but keep separate notation
Gesture replay trail lanesThree copies of the cached route sit in separate vertical lanes. Circle, diamond, and triangle marks distinguish linear, cubic ease-out, and spring in addition to color.LINEARCUBIC EASE-OUTDAMPED COSINE
Lookups
31
Progress span
0.27
Static base
0.64

Each colored tail performs 31 point lookups from the model's current progress back through a normalized distance span of 0.27. These are constructed score marks, not captured pointer history. Cyan, violet, and rose are also separated by −6, 0, and +6px and use radii 3.4, 4.1, and 4.8px.

The Canvas wash is derived from alpha 0.26 at 60Hz, so optical persistence stays approximately equivalent across refresh rates. If no custom point exists, one cached 72-point route supplies the score rather than being rebuilt every frame. Seed zero keeps the published route; other uint32 seeds add bounded harmonic variation. Reset clears transient captured input and restarts the current seeded route, while the replay-model setting can show all three markers or isolate one comparison.

Reduced motion disables capture, returns touch behavior to vertical page panning, draws one static frame at p=0.64, and schedules no RAF. The visible static positions are approximately 0.640, 0.953, and 0.979 before bounded path lookup. The Canvas remains aria-hidden. Its visible Pause / Resume transport now closes any active capture, rejects hidden input while paused, freezes the route and scene-local clock, and resumes from that same bounded record.

07 / Choose by intent

Choose the clock by task, not by claims of naturalness.

This scene is useful for explaining timing tokens, comparing endpoint arrival, and showing how one geometric route changes under three progress maps. It is not evidence that one model feels universally precise, soft, playful, or human.

Use this distance score when…

  • the lesson is how easing redistributes a fixed duration;
  • constant geometric speed is a useful linear baseline;
  • endpoint clamp and settle behavior should be visible;
  • the path remains illustrative rather than task-critical input.

Use another parameter when…

  • handwriting cadence or pauses must survive replay;
  • release velocity should drive momentum;
  • gesture recognition needs the full, unshifted path;
  • serialized input must reproduce exactly across devices.

Three catalog patterns clarify the boundary. Elastic Text Custom Easing Tradeoffs compares bounded CSS overshoot with stateful spring needs. Signature Line Drawing Illusion Overview shows pacing authored along a path rather than recovered from this sampler. Button Ripple Radial Gradient Fill demonstrates pointer-origin feedback that needs no route history at all.

The useful question is not which curve looks most alive. It is which information the interface must preserve: distance, recorded time, recent velocity, or only the fact that an action occurred.

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.