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- Linear
- 0.350
- Cubic ease-out
- 0.725
- Damped cosine
- 1.115
On this page
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.
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.
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);
}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.
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.
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
- Damped cosine
- Raw spring above 1
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.
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.
- 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.
- Pointer Events Level 3W3C
Pointer sampling, primary-button filtering, capture, and cancellation semantics.
- CSS Easing Functions Level 2W3C
Normative definitions for linear and cubic Bézier timing functions used in the comparison.