Merge nucleic/calm-willow-ibis-dozm into main
Mirrored from corpo 5e27961.
This commit is contained in:
+40
-65
@@ -1560,50 +1560,32 @@ const canonical = new URL('/', Astro.site);
|
||||
{ capture: true, passive: false },
|
||||
);
|
||||
|
||||
/* One swipe, one product — the finger's version of the rule above, and it has
|
||||
to be taken over rather than damped: the momentum a touch scroller hands back
|
||||
after the finger has gone is the whole of the problem, since it carries a
|
||||
flick clean across the stack and there is nothing left holding the glass to
|
||||
ask it not to. So the swipe is given the product it starts on and the one
|
||||
either side of it, tracks the finger exactly between them, and past them
|
||||
takes the finger's weight and returns a fifth of it, the way a scroller
|
||||
resists at its end. Lifting off settles on the product on the stage. A tap is
|
||||
untouched — nothing is prevented until the finger has actually moved — and so
|
||||
is a second finger, which is the reader zooming and not scrolling at all. */
|
||||
const TOUCH_STRETCH = 0.2;
|
||||
/* And never further than this, however long the swipe goes on. A resistance
|
||||
that only ever divides still arrives at the next product eventually, and the
|
||||
reader sees it take the stage and get taken off again on the way back — the
|
||||
one thing the whole rule is here to prevent. A fifteenth of the screen is far
|
||||
enough to read as give and much nearer than the next card's edge. */
|
||||
const TOUCH_STRETCH_MAX = 0.06;
|
||||
/* One swipe, one product, and nothing else about the swipe is read. A finger
|
||||
asking for the next product asks the same thing whether it travels an inch or
|
||||
the length of the screen, so the stack does not follow it: it steps once, the
|
||||
moment the gesture is unmistakably a swipe, and ignores the rest of it. That
|
||||
is also why there is no resistance anywhere below — nothing is tracking the
|
||||
finger, so there is nothing for the reader to push against and nothing to
|
||||
spring back from. It replaces the native scroller rather than damping it,
|
||||
because the momentum a touch scroller hands back after the finger has gone
|
||||
carries a flick clean across the stack and there is nothing left holding the
|
||||
glass to ask it not to. A tap never reaches the threshold; a second finger is
|
||||
a reader zooming and not scrolling at all. */
|
||||
const SWIPE_MIN_PX = 24;
|
||||
|
||||
/* Where the finger went down — the swipe is measured from there rather than
|
||||
from the last move, so a slow drag accumulates to the same threshold a flick
|
||||
crosses at once. */
|
||||
let touchY: number | null = null;
|
||||
let touchBeat = 0;
|
||||
let touchGive = 0;
|
||||
/* A tap is a finger that never moved, and settling under one would scroll the
|
||||
page out from under the link being followed. */
|
||||
let touchMoved = false;
|
||||
/* Where the swipe has asked to be, before any of the resistance below: reading
|
||||
it back off the element instead would compound the resistance against itself,
|
||||
and the finger would meet a wall rather than a spring. */
|
||||
let touchTop = 0;
|
||||
let touchLow = 0;
|
||||
let touchHigh = 0;
|
||||
let touchX = 0;
|
||||
let touchTaken = false;
|
||||
|
||||
document.addEventListener(
|
||||
'touchstart',
|
||||
(event) => {
|
||||
touchY = event.touches.length === 1 ? event.touches[0]?.clientY ?? null : null;
|
||||
if (touchY === null) return;
|
||||
|
||||
measureBeats();
|
||||
touchMoved = false;
|
||||
touchBeat = technologyAtScroll();
|
||||
touchTop = scrollRoot.scrollTop;
|
||||
touchLow = beatRest(touchBeat - 1);
|
||||
touchHigh = beatRest(touchBeat + 1);
|
||||
touchGive = scrollRoot.clientHeight * TOUCH_STRETCH_MAX;
|
||||
touchX = event.touches[0]?.clientX ?? 0;
|
||||
touchTaken = false;
|
||||
},
|
||||
{ capture: true, passive: true },
|
||||
);
|
||||
@@ -1611,45 +1593,38 @@ const canonical = new URL('/', Astro.site);
|
||||
document.addEventListener(
|
||||
'touchmove',
|
||||
(event) => {
|
||||
const nextTouchY = event.touches[0]?.clientY;
|
||||
if (touchY === null || nextTouchY === undefined) return;
|
||||
const finger = event.touches[0];
|
||||
if (touchY === null || !finger) return;
|
||||
|
||||
event.preventDefault();
|
||||
touchMoved = true;
|
||||
touchTop += touchY - nextTouchY;
|
||||
touchY = nextTouchY;
|
||||
if (touchTaken) return;
|
||||
|
||||
const bound = Math.min(touchHigh, Math.max(touchLow, touchTop));
|
||||
const past = touchTop - bound;
|
||||
/* Starts out giving back a fifth and closes on the cap: the same curve a
|
||||
scroller bounces with, so the swipe meets a spring rather than a wall. */
|
||||
const give =
|
||||
Math.sign(past) *
|
||||
touchGive *
|
||||
(1 - 1 / (1 + (TOUCH_STRETCH * Math.abs(past)) / touchGive));
|
||||
const travelled = touchY - finger.clientY;
|
||||
/* More across than down is a reader swiping at the browser rather than at
|
||||
the stack, and the stack is not what should answer. */
|
||||
if (
|
||||
Math.abs(travelled) < SWIPE_MIN_PX ||
|
||||
Math.abs(travelled) < Math.abs(touchX - finger.clientX)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollRoot.scrollTop = bound + give;
|
||||
touchTaken = true;
|
||||
measureBeats();
|
||||
scrollRoot.scrollTo({
|
||||
top: beatRest(technologyAtScroll() + Math.sign(travelled)),
|
||||
behavior: reducedMotion.matches ? 'auto' : 'smooth',
|
||||
});
|
||||
},
|
||||
{ capture: true, passive: false },
|
||||
);
|
||||
|
||||
/* The stage is asked where it is rather than told, so a swipe that gave up
|
||||
halfway settles back where it came from — but never further than the one
|
||||
product the swipe was granted, whatever the stretch above let through. */
|
||||
const settleTouch = () => {
|
||||
if (touchY === null) return;
|
||||
const endTouch = () => {
|
||||
touchY = null;
|
||||
if (!touchMoved) return;
|
||||
|
||||
const beat = Math.min(touchBeat + 1, Math.max(touchBeat - 1, technologyAtScroll()));
|
||||
scrollRoot.scrollTo({
|
||||
top: beatRest(beat),
|
||||
behavior: reducedMotion.matches ? 'auto' : 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('touchend', settleTouch, { capture: true, passive: true });
|
||||
document.addEventListener('touchcancel', settleTouch, { capture: true, passive: true });
|
||||
document.addEventListener('touchend', endTouch, { capture: true, passive: true });
|
||||
document.addEventListener('touchcancel', endTouch, { capture: true, passive: true });
|
||||
}
|
||||
|
||||
/* Where the band below reaches: the story hands over to a card when that card's
|
||||
|
||||
Reference in New Issue
Block a user