From cd31b76889074666df4daea42f0f0de7c8c175fa Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Tue, 3 Nov 2026 13:28:43 -0500 Subject: [PATCH] gtauri viewer: velocity-scaled swipe commit (faster flicks) Flicks now continue the finger's motion instead of taking a fixed 200 ms: commit duration = remaining distance / release velocity (15 % snappier), clamped 80-240 ms, ease-out. A full-speed flick releasing past half the screen commits in ~80 ms (was 200). Slow releases past the 25 % threshold still settle gently (180 ms smoothstep; 150 ms for keyboard nav). Verified on the emulator: a 100 ms flick is fully settled before the first post-release screencap (~150 ms), with only a 5 px edge artifact between consecutive frames (no residual motion). --- harnesses/gtauri/frontend/dist/index.html | 30 +++++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/harnesses/gtauri/frontend/dist/index.html b/harnesses/gtauri/frontend/dist/index.html index eccc433..7826f03 100644 --- a/harnesses/gtauri/frontend/dist/index.html +++ b/harnesses/gtauri/frontend/dist/index.html @@ -546,26 +546,40 @@ function tweenTo(tx, ty, ms, onDone) { /// Both images stay on the page strip for the whole animation: /// the incoming one slides home while the outgoing one slides off /// screen; only then do the roles swap (returns false at the edges). -function navigate(delta) { +function navigate(delta, vel) { if (tween && tween.commit) return false; // commit already in flight const i = ids.indexOf(viewerId); const ni = i + delta; if (i < 0 || ni < 0 || ni >= ids.length) return false; - commitTo(ni, delta); + commitTo(ni, delta, vel || 0); return true; } /// Commit a swipe: animate vTx to the outgoing offset (the incoming /// preview tracks at vTx + delta*W and lands exactly on 0), then swap /// the elements' roles. The commit tween is locked: input is ignored -/// until it finishes (~200 ms). -function commitTo(ni, delta) { +/// until it finishes. `vel` = release velocity in px/ms: a flick +/// continues the finger's motion (duration scales with the remaining +/// distance at that velocity, ease-out — fast flicks are fast), a +/// slow release settles gently. +function commitTo(ni, delta, vel) { navSet(ids[ni], delta); // the incoming (no-op if the drag showed it) const out = -delta * innerWidth; - const x0 = vTx, t0 = performance.now(), ms = 200; + const x0 = vTx, t0 = performance.now(); + let ms, ease; + if (vel > 0.35) { + const remain = Math.abs(out - x0); + // Time to cover the remaining distance at the release velocity, + // 15 % snappier than the raw continuation, clamped 80–240 ms. + ms = Math.max(80, Math.min(240, remain / vel * 0.85)); + ease = (t) => 1 - Math.pow(1 - t, 3); // ease-out: land soft + } else { + ms = vel > 0 ? 180 : 150; // drag-settle / keyboard + ease = (t) => t * t * (3 - 2 * t); + } const step = (now) => { const t = Math.min(1, (now - t0) / ms); - const e = t * t * (3 - 2 * t); // smoothstep: pick up, land soft + const e = ease(t); vTx = x0 + (out - x0) * e; vTy = 0; applyTransform(); @@ -690,9 +704,9 @@ const endPt = (e) => { // Commit past the threshold; at the library edges navigate // refuses (nothing to commit to) and we snap back instead. if (dx < -w * 0.25 || g.vx < -0.35) { - if (!navigate(+1)) tweenTo(0, 0, 120, navHide); + if (!navigate(+1, Math.abs(g.vx))) tweenTo(0, 0, 120, navHide); } else if (dx > w * 0.25 || g.vx > 0.35) { - if (!navigate(-1)) tweenTo(0, 0, 120, navHide); + if (!navigate(-1, Math.abs(g.vx))) tweenTo(0, 0, 120, navHide); } else { tweenTo(0, 0, 120, navHide); // not far enough: snap back }