diff options
| author | Adam Soltys <[email protected]> | 2026-10-13 23:36:16 +0000 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-13 23:36:16 +0000 |
| commit | 9ce87d8742ea44918e73625cc79a0fc184b4597c (patch) | |
| tree | 6e5cc0cf40516d1dc576871fde75fdaa78c02a42 /src | |
| parent | 35feb53c50cf5662aca40e9af5a0547e36297631 (diff) | |
| download | coinosv3-9ce87d8742ea44918e73625cc79a0fc184b4597c.tar.gz coinosv3-9ce87d8742ea44918e73625cc79a0fc184b4597c.zip | |
Pager swipes: follow the finger sooner, page on a shorter swipe, glide faster, no step back
- The glide was timed from now, but a frame's time can be a little earlier:
its first frame stepped back (72px -> 58) before going on, the choppy
'reset' on letting go. Timed from its first frame now.
- Slides follow the finger after 10px (was 30: a dead zone, then a jump).
A touch that comes and goes within 24px and 350ms is still a tap.
- On letting go: on to the next past a tenth of the width across, an eighth
of the height down, or a flick of 24px at 0.2px/ms (was a fifth, 48px, 0.3).
- Glide at most 130ms (was 170).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/messages.js | 32 |
1 files changed, 19 insertions, 13 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 6dbb4fd..55a4376 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -12442,10 +12442,13 @@ export function messagesFeature(ctx) { box.style.scrollSnapType = 'none'; const done = () => { box._glide = 0; box.style.scrollSnapType = ''; }; if (Math.abs(dist) < 1) { done(); return; } - const ms = Math.max(80, Math.min(170, Math.abs(dist) / Math.max(3.5, Math.abs(vel) * 1.5))); - const t0 = performance.now(); + const ms = Math.max(60, Math.min(130, Math.abs(dist) / Math.max(5, Math.abs(vel) * 1.5))); + // timed from its first frame: a frame's time can be a little before now, + // and a glide timed from now began by stepping back + let t0 = 0; const step = (now) => { - const k = Math.min(1, (now - t0) / ms), e = 1 - Math.pow(1 - k, 3); + if (!t0) t0 = now - 8; // (a frame's worth in already: no frame stands still) + const k = Math.max(0, Math.min(1, (now - t0) / ms)), e = 1 - Math.pow(1 - k, 3); if (y) box.scrollTop = from + dist * e; else box.scrollLeft = from + dist * e; if (k < 1) box._glide = requestAnimationFrame(step); else done(); }; @@ -12472,13 +12475,14 @@ export function messagesFeature(ctx) { // A touch is the pager's to judge, not the browser's. Left to native // scrolling, a tap whose finger moved a few pixels was taken for a // scroll (no click: a tap meant to pause a clip did nothing), and a quick - // one flung the pager on to the next slide. So the pager holds the slides - // still until a touch has gone TAP_SLOP: short of that it's a tap (done - // here if the browser sent no click). Past it, the slides follow the - // finger along the way it set off — up and down post to post, across - // within a post — and on letting go go on to the next if the finger went - // a fifth of the way or flicked, else back. - const TAP_SLOP = 30, SWIPE_FRAC = 0.2, FLICK = 0.3, FLICK_MIN = 48; // px; of the screen; px/ms; px + // one flung the pager on to the next slide. So the slides follow the + // finger once it has gone FOLLOW, along the way it set off — up and down + // post to post, across within a post — and on letting go go on to the + // next if the finger went a little way or flicked, else back. A touch + // that came and went within TAP_SLOP is a tap all the same (done here if + // the browser sent no click): a tap meant to pause a clip drifts. + const FOLLOW = 10, TAP_SLOP = 24, TAP_MS = 350; // px; px; ms + const SWIPE_X = 0.1, SWIPE_Y = 0.12, FLICK = 0.2, FLICK_MIN = 24; // of the screen; px/ms; px let touch = null, pendingTap = 0, tapDoneAt = 0; el.addEventListener('touchstart', (e) => { const t = e.touches[0]; @@ -12493,7 +12497,7 @@ export function messagesFeature(ctx) { if (e.cancelable) e.preventDefault(); const t = e.touches[0], dx = t.clientX - d.x, dy = t.clientY - d.y; if (!d.axis) { - if (Math.hypot(dx, dy) < TAP_SLOP) return; + if (Math.hypot(dx, dy) < FOLLOW) return; d.axis = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y'; d.box = d.axis === 'y' ? pager.scroller : d.slide && d.slide.parentElement; if (!d.box) { touch = null; return; } @@ -12503,6 +12507,7 @@ export function messagesFeature(ctx) { d.track = []; } const delta = d.axis === 'y' ? dy : dx; + d.far = Math.max(d.far || 0, Math.hypot(dx, dy)); if (d.axis === 'y') d.box.scrollTop = d.start - delta; else d.box.scrollLeft = d.start - delta; d.track.push({ t: performance.now(), v: delta }); // (when handled: synthetic moves can share a timeStamp) if (d.track.length > 5) d.track.shift(); @@ -12526,9 +12531,10 @@ export function messagesFeature(ctx) { const size = d.axis === 'y' ? d.box.clientHeight : d.box.clientWidth; const a = d.track[0], b = d.track[d.track.length - 1]; const vel = a && b && b.t > a.t ? (b.v - a.v) / (b.t - a.t) : 0; - const go = Math.abs(delta) > size * SWIPE_FRAC || (Math.abs(delta) >= FLICK_MIN && Math.abs(vel) > FLICK && Math.sign(vel) === Math.sign(delta)); + const tap = d.far < TAP_SLOP && Date.now() - d.at < TAP_MS; + const go = !tap && (Math.abs(delta) > size * (d.axis === 'y' ? SWIPE_Y : SWIPE_X) || (Math.abs(delta) >= FLICK_MIN && Math.abs(vel) > FLICK && Math.sign(vel) === Math.sign(delta))); settle(d, go ? (delta < 0 ? 1 : -1) : 0, go ? vel : 0); - return; + if (!tap) return; } if (Date.now() - d.at > 500) return; // a press, not a tap clearTimeout(pendingTap); |
