mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 07:42:23 +00:00
Make one scroll at a rail's edge step one panel
A swipe is still speeding up when it first steps, and that rise in speed was read as a fresh flick, so one swipe could step two panels. A flick now counts only after the gesture has clearly slowed and a short moment after a step; a mouse wheel notch still steps each time. The rest of a gesture that has stepped is used up even when the pointer is no longer in the strip, so its coasting does not zoom the globe or scroll what opened under it. The strip is never wider than a rail's own column, a panel in either stack keeps its own scrolling, and the slide classes come off without an animation to end them (reduced motion). Co-authored-by: Sameh Khamis <[email protected]>
This commit is contained in:
1 parent
7ee047961b
commit
b080857c35
1 file changed
+62
-17
+62
-17
@@ -14,9 +14,18 @@ const STEP_DELTA = 40;
|
||||
* gesture moves one step, however long it runs, trackpad coasting included.
|
||||
*/
|
||||
const GESTURE_GAP_MS = 180;
|
||||
/** Things in the strip that keep their own pointer and wheel behaviour. */
|
||||
/** After a step, how long (ms) before the same motion can count as a new flick. */
|
||||
const STEP_COOLDOWN_MS = 220;
|
||||
/** A wheel event this large (pixels) is a mouse wheel notch, not a trackpad. */
|
||||
const NOTCH_DELTA = 100;
|
||||
/** The widest the strip gets, measured from the screen edge, whatever shape the rail takes. */
|
||||
const STRIP_MAX = 84;
|
||||
/**
|
||||
* Things in the strip that keep their own pointer and wheel behaviour: any
|
||||
* control, and every panel in either side's stack (a panel scrolls itself).
|
||||
*/
|
||||
const OWN_CONTROLS =
|
||||
'a, button, input, select, textarea, [role="dialog"], .ui-panel, #first-run-launcher, #cesium-credits';
|
||||
'a, button, input, select, textarea, [role="dialog"], .ui-panel, #left-panel-stack > :not(.gev-panel-rail), #right-context-rail > :not(.gev-panel-rail), #first-run-launcher, #cesium-credits';
|
||||
|
||||
/**
|
||||
* @param {Document} [doc]
|
||||
@@ -32,6 +41,10 @@ export function installRailEdgeScroll(doc = globalThis.document) {
|
||||
let lastWheel = 0;
|
||||
let lastSpeed = 0;
|
||||
let stepped = false;
|
||||
/** When the last step was taken, the fastest the gesture has been, and whether it has slowed since. */
|
||||
let steppedAt = 0;
|
||||
let peakSpeed = 0;
|
||||
let slowed = false;
|
||||
let frame = 0;
|
||||
|
||||
const rails = () =>
|
||||
@@ -103,10 +116,14 @@ export function installRailEdgeScroll(doc = globalThis.document) {
|
||||
if (doc.body.classList.contains('mobile-shell') || coarse?.matches) return null;
|
||||
for (const { side, rail } of rails()) {
|
||||
const box = rail.getBoundingClientRect();
|
||||
// From the screen edge to just past the rail, but never wider than a
|
||||
// rail's own column: a rail laid out as a row across the top of its
|
||||
// panels would otherwise claim most of the panel's width.
|
||||
const width = doc.documentElement.clientWidth;
|
||||
const inStrip =
|
||||
side === 'left'
|
||||
? x <= box.right + STRIP_MARGIN
|
||||
: x >= box.left - STRIP_MARGIN;
|
||||
? x <= Math.min(box.right + STRIP_MARGIN, STRIP_MAX)
|
||||
: x >= Math.max(box.left - STRIP_MARGIN, width - STRIP_MAX);
|
||||
if (!inStrip) continue;
|
||||
if (rail.contains(target)) return rail;
|
||||
if (target?.closest?.(OWN_CONTROLS)) return null;
|
||||
@@ -151,37 +168,65 @@ export function installRailEdgeScroll(doc = globalThis.document) {
|
||||
panel.classList.remove('rail-switch-down', 'rail-switch-up');
|
||||
void panel.offsetWidth;
|
||||
panel.classList.add(direction > 0 ? 'rail-switch-down' : 'rail-switch-up');
|
||||
panel.addEventListener(
|
||||
'animationend',
|
||||
() => panel.classList.remove('rail-switch-down', 'rail-switch-up'),
|
||||
{ once: true },
|
||||
);
|
||||
// The classes come off when the slide ends, or after a moment where
|
||||
// there is no slide to end (reduced motion).
|
||||
const settle = () => {
|
||||
win.clearTimeout(timer);
|
||||
panel.removeEventListener('animationend', settle);
|
||||
panel.classList.remove('rail-switch-down', 'rail-switch-up');
|
||||
};
|
||||
const timer = win.setTimeout(settle, 600);
|
||||
panel.addEventListener('animationend', settle);
|
||||
}
|
||||
follow();
|
||||
}
|
||||
|
||||
function onWheel(event) {
|
||||
if (!hot || event.ctrlKey) return;
|
||||
if (event.ctrlKey) return;
|
||||
const now = win.performance?.now?.() ?? Date.now();
|
||||
const inGesture = now - lastWheel <= GESTURE_GAP_MS;
|
||||
// Recheck: the pointer may not have moved since the panels changed.
|
||||
if (railAt(event.clientX, event.target) !== hot) return setHot(null);
|
||||
const stillHot = Boolean(hot) && railAt(event.clientX, event.target) === hot;
|
||||
if (!stillHot) {
|
||||
setHot(null);
|
||||
// The rest of a gesture that has already stepped is used up here, so
|
||||
// its coasting does not go on to zoom the globe or scroll what opened
|
||||
// under the pointer.
|
||||
if (stepped && inGesture) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
lastWheel = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const now = win.performance?.now?.() ?? Date.now();
|
||||
const speed = Math.abs(event.deltaY);
|
||||
// A new gesture: after a pause, after turning round, or a fresh flick
|
||||
// while the last one is still coasting (the speed jumps back up).
|
||||
// A new gesture: after a pause, after turning round, a mouse wheel notch,
|
||||
// or a fresh flick while the last one is still coasting. A flick is a
|
||||
// rise in speed after the gesture has clearly slowed, and not in the
|
||||
// moment after a step: a swipe is still speeding up when it first steps.
|
||||
const turned = travel && Math.sign(event.deltaY) !== Math.sign(travel);
|
||||
const flick = stepped && speed > 20 && speed > lastSpeed * 2;
|
||||
if (now - lastWheel > GESTURE_GAP_MS || turned || flick) {
|
||||
const cooled = now - steppedAt > STEP_COOLDOWN_MS;
|
||||
const notch = stepped && cooled && speed >= NOTCH_DELTA;
|
||||
const flick = stepped && cooled && slowed && speed > Math.max(8, lastSpeed * 1.8);
|
||||
if (!inGesture || turned || notch || flick) {
|
||||
travel = 0;
|
||||
stepped = false;
|
||||
peakSpeed = 0;
|
||||
slowed = false;
|
||||
}
|
||||
lastWheel = now;
|
||||
lastSpeed = speed;
|
||||
if (stepped) return;
|
||||
peakSpeed = Math.max(peakSpeed, speed);
|
||||
if (stepped) {
|
||||
if (speed < peakSpeed * 0.5) slowed = true;
|
||||
return;
|
||||
}
|
||||
travel += event.deltaY;
|
||||
if (Math.abs(travel) < STEP_DELTA) return;
|
||||
stepped = true;
|
||||
steppedAt = now;
|
||||
step(Math.sign(travel));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user