Merge nucleic/calm-willow-ibis-dozm into main
Mirrored from corpo 43dcea2.
This commit is contained in:
+34
-78
@@ -1531,70 +1531,32 @@ const canonical = new URL('/', Astro.site);
|
|||||||
return event.deltaY * multiplier;
|
return event.deltaY * multiplier;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Trackpad momentum arrives as a long tail of wheel deltas, and one hard flick
|
/* One flick, one product. A trackpad hands a hard flick over as a long tail of
|
||||||
carries enough of it to cross the whole stack — the products in the middle are
|
wheel deltas, enough of them to cross the whole stack with the products in
|
||||||
then only a flicker on the way past, which is the one thing the beat pacing
|
between only a flicker on the way past — and that tail is the system playing
|
||||||
above cannot fix, because by the time it has played a card the scroll is
|
out momentum, not a hand still asking for anything. What tells the two apart
|
||||||
already somewhere else. So a step entered at fling speed is entered slowly.
|
is that momentum only ever weakens: a finger on the pad can push as hard as it
|
||||||
The rule the whole thing reduces to: a gesture may cross any boundary it
|
likes, a tail cannot. So a delta that arrives no weaker than the one before it
|
||||||
reaches at reading pace, and the moment it crosses one at fling speed, what is
|
is the user, and is granted the product it has reached; a tail is granted the
|
||||||
left of that fling is held to a fifteenth of its weight. One flick, one
|
product it has reached and nothing beyond it. Push again mid-flick and the
|
||||||
product; a reader who keeps pushing still gets through, just against the
|
push counts, which is what lets a second flick take a second product instead
|
||||||
brakes. */
|
of dying against a brake — the hand is what the stack answers to, and its
|
||||||
const CATCH_STRENGTH = 0.95;
|
speed is not otherwise measured. */
|
||||||
/* The stack answers to a shorter gesture than the page it sits in — three
|
const TAIL_FALL = 0.97;
|
||||||
products across nine hundred pixels means a careful two-finger drag spends
|
const TAIL_DRAG = 0.05;
|
||||||
itself inside one of them, which reads as a page that has not noticed. So a
|
/* Long enough that a tail is still the gesture that threw it, short enough that
|
||||||
delta is worth a third again as much at reading pace, and that extra is given
|
a deliberate second flick is a new one. */
|
||||||
back as the gesture speeds up, reaching nothing at fling speed. The one curve
|
|
||||||
answers both halves of the complaint: slow gestures cover more ground, fast
|
|
||||||
ones cover less, and it is the same number saying so. */
|
|
||||||
const SLOW_GAIN = 1.35;
|
|
||||||
/* Below the first, a gesture is reading and is never touched; above the second
|
|
||||||
it is a fling. Both are per-frame, so they are speeds a hand can feel — about
|
|
||||||
840px and 2040px a second — rather than counts of an event nobody controls. */
|
|
||||||
const SLOW_FRAME_PX = 14;
|
|
||||||
const FAST_FRAME_PX = 34;
|
|
||||||
const GESTURE_GAP_MS = 140;
|
const GESTURE_GAP_MS = 140;
|
||||||
|
/* Three products across nine hundred pixels is a shorter run than the page this
|
||||||
|
sits in, so at page weight a careful two-finger drag spends itself inside one
|
||||||
|
of them, which reads as a page that has not noticed. */
|
||||||
|
const WHEEL_GAIN = 1.3;
|
||||||
|
|
||||||
let wheelAt = -Infinity;
|
let wheelAt = -Infinity;
|
||||||
let wheelSpeed = 0;
|
let lastPixels = 0;
|
||||||
|
/* The product this gesture is entitled to: re-granted on every delta the hand is
|
||||||
|
driving, frozen for as long as it is only momentum. */
|
||||||
let gestureBeat = -1;
|
let gestureBeat = -1;
|
||||||
/* How hard this gesture was going at the boundary it was caught at, held for the
|
|
||||||
rest of it: momentum decays, and a brake that eased off with it would let go
|
|
||||||
exactly when the tail is long enough to reach the next product. Nought while
|
|
||||||
the gesture is still free. */
|
|
||||||
let gestureCatch = 0;
|
|
||||||
|
|
||||||
/* Nought at reading pace, one at fling speed. Everything below reads the gesture
|
|
||||||
through this single number: how much a delta is worth, and how hard a boundary
|
|
||||||
pushes back once one has been crossed at speed. */
|
|
||||||
const wheelFast = () =>
|
|
||||||
Math.min(
|
|
||||||
1,
|
|
||||||
Math.max(0, (wheelSpeed - SLOW_FRAME_PX) / (FAST_FRAME_PX - SLOW_FRAME_PX)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const beatDrag = (fast: number) => {
|
|
||||||
const beat = technologyAtScroll();
|
|
||||||
if (beat === gestureBeat) return 1;
|
|
||||||
|
|
||||||
if (!gestureCatch) {
|
|
||||||
/* Reading pace: the gesture is entitled to the beat it has reached, and to
|
|
||||||
the next one it reaches the same way. Only speed is ever held back. */
|
|
||||||
if (fast <= 0) {
|
|
||||||
gestureBeat = beat;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
gestureCatch = fast;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Flat rather than a ramp that fades across the step. A ramp charges a fling
|
|
||||||
only the log of its weight — measured, it took barely a tenth off a hard
|
|
||||||
one — where a flat brake charges it in proportion, which is what makes a
|
|
||||||
flick spend itself inside one product instead of three. */
|
|
||||||
return 1 - CATCH_STRENGTH * gestureCatch;
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
'wheel',
|
'wheel',
|
||||||
@@ -1605,29 +1567,23 @@ const canonical = new URL('/', Astro.site);
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
const gap = now - wheelAt;
|
|
||||||
const pixels = wheelPixels(event);
|
const pixels = wheelPixels(event);
|
||||||
/* Per frame rather than per event, since a mouse notch and a trackpad
|
const driven =
|
||||||
sample arrive at nothing like the same rate. The gap is held between a
|
now - wheelAt > GESTURE_GAP_MS ||
|
||||||
quarter frame and four: below, two events sharing a millisecond would
|
/* A reversal is a fresh intention whatever its weight. */
|
||||||
read as impossibly fast, and above, the first event of a gesture would
|
pixels * lastPixels <= 0 ||
|
||||||
read as a crawl. */
|
Math.abs(pixels) >= Math.abs(lastPixels) * TAIL_FALL;
|
||||||
const frames = Math.max(4, Math.min(gap, 64)) / 16;
|
|
||||||
const instant = Math.abs(pixels) / frames;
|
|
||||||
|
|
||||||
if (gap > GESTURE_GAP_MS) {
|
|
||||||
wheelSpeed = instant;
|
|
||||||
gestureBeat = technologyAtScroll();
|
|
||||||
gestureCatch = 0;
|
|
||||||
} else {
|
|
||||||
wheelSpeed = wheelSpeed * 0.65 + instant * 0.35;
|
|
||||||
}
|
|
||||||
wheelAt = now;
|
wheelAt = now;
|
||||||
|
lastPixels = pixels;
|
||||||
|
|
||||||
const fast = wheelFast();
|
const beat = technologyAtScroll();
|
||||||
const gain = 1 + (SLOW_GAIN - 1) * (1 - fast);
|
if (driven) gestureBeat = beat;
|
||||||
|
|
||||||
scrollRoot.scrollBy({ top: pixels * gain * beatDrag(fast), behavior: 'auto' });
|
scrollRoot.scrollBy({
|
||||||
|
top: pixels * WHEEL_GAIN * (beat === gestureBeat ? 1 : TAIL_DRAG),
|
||||||
|
behavior: 'auto',
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{ capture: true, passive: false },
|
{ capture: true, passive: false },
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user