aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-15 08:46:22 -0700
committerAdam Soltys <[email protected]>2026-10-15 08:46:22 -0700
commit6ecc636c4a16b343f07d65c953c678762a1f175a (patch)
tree8ddc2bed2c5941b15e894447c2638ac646794ae7 /src/features
parent849e3d172354c87c14991392bd075334dc5f68af (diff)
downloadcoinosv3-6ecc636c4a16b343f07d65c953c678762a1f175a.tar.gz
coinosv3-6ecc636c4a16b343f07d65c953c678762a1f175a.zip
Media pager: a fast flick on the phone flies past several posts and lands snapped on one. A swipe went one post, however hard it was flung (Adam, 2026-10-16: "can we make images coast on the phone too when I swipe?"). Now a flick of FLY_V (3 px/ms) or more flies as many posts as the fling would have coasted — its speed times FLY_TAU (500 ms), in screens, up to six — in a glide, the pictures passing, to land square on the last (pagerFlyTo, the flight factored out of the wheel's pagerFly, paced a little slower per screen for a finger); only the landing is arrived at. A gentler swipe is the one post it was. A fling that would go past the end lands on the last post (or the first), and a swipe from there wraps by one post as before (a fling's whole count no longer picks the wrap target); a finger put down mid-flight catches it where it is. tools/pager-swipe-fly-test.js (a 320px swipe over 130ms is one post; a 420px flick in 72ms is three; the counter agrees; near the end it lands on the last and the next flick wraps to the first; a finger catches a flight) — the quick flick is dispatched inside the page, since the browser's touch simulation takes a round trip per move; pager-tap-slop and profile-pager tests pass.
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01J1Kon8bxJoHRpJdSzshCkp
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js29
1 files changed, 24 insertions, 5 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index d18f6df..4a154c8 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -12652,11 +12652,17 @@ export function messagesFeature(ctx) {
const to = stops[i];
fly = { to, n, at: now, dir };
if (live) pager.scroller._onDone = null; // the flight goes on: not a landing
+ pagerFlyTo(to);
+ }
+ // The flight itself: a glide to the post, the pictures passing, landing
+ // snapped on it — only the landing is arrived at. A flick of a finger
+ // (pace: a little slower per screen than the wheel's) comes here too.
+ function pagerFlyTo(to, { vel = 0, pace = 110 } = {}) {
const dest = pagerAt(pager.scroller, to, true);
const screens = Math.abs(dest - pager.scroller.scrollTop) / Math.max(1, pager.scroller.clientHeight);
pager.flying = true;
- pagerGlide(pager.scroller, 'y', dest, 0, {
- ms: screens > 1.05 ? Math.min(1100, 100 + 110 * screens) : 0,
+ pagerGlide(pager.scroller, 'y', dest, vel, {
+ ms: screens > 1.05 ? Math.min(1200, 100 + pace * screens) : 0,
onDone: () => { if (!pager) return; pager.flying = false; const s = pagerHere(); if (s && s !== pager.cur) pagerArrive(s); },
});
}
@@ -12751,6 +12757,13 @@ export function messagesFeature(ctx) {
// 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
+ // A fast flick up or down flies past several posts, the way a spin of
+ // the wheel does, and lands snapped on one (Adam, 2026-10-16: "make
+ // images coast on the phone too when I swipe"): as many posts as the
+ // fling would have coasted — the speed times FLY_TAU, in screens — from
+ // FLY_V up; a slower swipe is the one post it was. The ends are not
+ // flown past: a fling that would, lands on the last (or first) post.
+ const FLY_V = 3, FLY_TAU = 500, FLY_MAX = 6; // px/ms; ms; posts
let touch = null, pendingTap = 0, tapDoneAt = 0;
el.addEventListener('touchstart', (e) => {
const t = e.touches[0];
@@ -12785,10 +12798,16 @@ export function messagesFeature(ctx) {
const box = d.box, y = d.axis === 'y';
const stops = [...box.children].filter((c) => c.matches(y ? '.mp-group' : '.mp-slide')).map((c) => pagerAt(box, c, y));
if (!stops.length) return;
- // the stop it set off from, then one along (or back)
+ // the stop it set off from, then one along (or back) — or, flung, several
const from = stops.reduce((bi, p, i) => (Math.abs(p - d.start) < Math.abs(stops[bi] - d.start) ? i : bi), 0);
- const next = from + step;
- if (y && (next < 0 || next >= stops.length)) { pagerWrap(pagerGroups()[(next + stops.length) % stops.length]); return; }
+ const posts = y && step && Math.abs(vel) >= FLY_V ? Math.min(FLY_MAX, Math.max(1, Math.round(Math.abs(vel) * FLY_TAU / Math.max(1, box.clientHeight)))) : 1;
+ const next = from + step * posts;
+ if (y && posts > 1) {
+ const i = Math.min(stops.length - 1, Math.max(0, next));
+ if (i !== from) { pagerFlyTo(pagerGroups()[i], { vel, pace: 150 }); return; }
+ }
+ // past either end: round to the other (a fling at the end too — one post of wrap, not its whole count)
+ if (y && (next < 0 || next >= stops.length)) { pagerWrap(pagerGroups()[(from + step + stops.length) % stops.length]); return; }
pagerGlide(box, d.axis, stops[Math.min(stops.length - 1, Math.max(0, next))], vel);
}
el.addEventListener('touchend', (e) => {