diff options
| author | Adam Soltys <[email protected]> | 2026-10-15 08:46:22 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-15 08:46:22 -0700 |
| commit | 6ecc636c4a16b343f07d65c953c678762a1f175a (patch) | |
| tree | 8ddc2bed2c5941b15e894447c2638ac646794ae7 /src/features | |
| parent | 849e3d172354c87c14991392bd075334dc5f68af (diff) | |
| download | coinosv3-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.js | 29 |
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) => { |
