From 35feb53c50cf5662aca40e9af5a0547e36297631 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 13 Oct 2026 18:32:23 +0000 Subject: Pager glide: land on the slide itself, no overshoot and snap-back; quicker The glide aimed at n x the scroller's height, but a post is 100dvh, a hair off it on a phone; the error grew post by post until it landed past the slide and the snap yanked it back. It now aims at the measured position of the next slide. Faster too: at most 170ms (lands in ~195ms from lift-off). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt --- src/features/messages.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'src/features') diff --git a/src/features/messages.js b/src/features/messages.js index a7dc54c..6dbb4fd 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -12442,7 +12442,7 @@ 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(90, Math.min(220, Math.abs(dist) / Math.max(2.5, Math.abs(vel) * 1.5))); + 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 step = (now) => { const k = Math.min(1, (now - t0) / ms), e = 1 - Math.pow(1 - k, 3); @@ -12451,6 +12451,13 @@ export function messagesFeature(ctx) { }; box._glide = requestAnimationFrame(step); } + // Where a slide (or post) sits in its scroller — measured, not counted in + // screens: a post is 100dvh, a hair off the scroller's height, and n + // screens down landed past it for the snap to yank back. + const pagerAt = (box, el, y) => { + const b = box.getBoundingClientRect(), r = el.getBoundingClientRect(); + return y ? box.scrollTop + r.top - b.top : box.scrollLeft + r.left - b.left; + }; const pagerHold = (box) => { if (box && box._glide) { cancelAnimationFrame(box._glide); box._glide = 0; } }; function buildPager(want) { @@ -12503,10 +12510,11 @@ export function messagesFeature(ctx) { // let go of a drag: on to the next (or the one before), or back function settle(d, step, vel = 0) { const box = d.box, y = d.axis === 'y'; - const size = (y ? box.clientHeight : box.clientWidth) || 1; - const last = Math.max(0, Math.round(((y ? box.scrollHeight : box.scrollWidth) - size) / size)); - const to = Math.min(last, Math.max(0, Math.round(d.start / size) + step)); - pagerGlide(box, d.axis, to * size, vel); + const stops = [...box.children].filter((c) => c.matches(y ? '.mp-group, .mp-tail' : '.mp-slide')).map((c) => pagerAt(box, c, y)); + if (!stops.length) return; + // the stop it set off from, then one along (or back) + const from = stops.reduce((bi, p, i) => (Math.abs(p - d.start) < Math.abs(stops[bi] - d.start) ? i : bi), 0); + pagerGlide(box, d.axis, stops[Math.min(stops.length - 1, Math.max(0, from + step))], vel); } el.addEventListener('touchend', (e) => { const d = touch; @@ -12734,13 +12742,13 @@ export function messagesFeature(ctx) { e.preventDefault(); const from = pager.onTail || !here ? pager.tail : here.closest('.mp-group'); const to = from && (v > 0 ? from.nextElementSibling : from.previousElementSibling); - if (to) pagerGlide(pager.scroller, 'y', to.offsetTop); + if (to) pagerGlide(pager.scroller, 'y', pagerAt(pager.scroller, to, true)); return; } if (hz) { e.preventDefault(); const to = !pager.onTail && here && (hz > 0 ? here.nextElementSibling : here.previousElementSibling); - if (to) pagerGlide(here.parentElement, 'x', to.offsetLeft); + if (to) pagerGlide(here.parentElement, 'x', pagerAt(here.parentElement, to, false)); return; } if (e.key === ' ' && pager.cur) { -- cgit v1.2.3