aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-13 23:36:16 +0000
committerAdam Soltys <[email protected]>2026-10-13 23:36:16 +0000
commit9ce87d8742ea44918e73625cc79a0fc184b4597c (patch)
tree6e5cc0cf40516d1dc576871fde75fdaa78c02a42 /src
parent35feb53c50cf5662aca40e9af5a0547e36297631 (diff)
downloadcoinosv3-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.js32
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);