aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js62
1 files changed, 31 insertions, 31 deletions
diff --git a/src/app.js b/src/app.js
index 3cdb59e..dae7b17 100644
--- a/src/app.js
+++ b/src/app.js
@@ -695,9 +695,16 @@ function attachZoom(box, img, { wheel = true } = {}) {
if (e.target.closest('.lightbox-x')) return;
if (moved) { moved = false; e.stopImmediatePropagation(); e.preventDefault(); return; }
}, true);
+ // The wheel. true: a turn zooms (the lightbox, a picture on its own).
+ // 'ctrl': ctrl+wheel zooms — a trackpad's pinch arrives in Chrome as
+ // exactly that — and a plain wheel pans the picture while it is zoomed;
+ // at 1× a plain wheel is left to whoever owns the box (the pager pages).
if (wheel) box.addEventListener('wheel', (e) => {
+ const zoom = wheel !== 'ctrl' || e.ctrlKey || e.metaKey;
+ if (!zoom && s <= 1) return;
e.preventDefault();
- zoomAt(s * Math.exp(-e.deltaY * 0.002), e.clientX, e.clientY);
+ if (zoom) zoomAt(s * Math.exp(-e.deltaY * (wheel === 'ctrl' ? 0.004 : 0.002)), e.clientX, e.clientY);
+ else { x -= e.deltaX; y -= e.deltaY; }
clamp(); apply(false);
}, { passive: false });
}
@@ -6646,63 +6653,56 @@ if (INTENT_URI) {
if (!raf && v) { last = performance.now(); raf = requestAnimationFrame(tick); }
};
// ---- the wheel
- // A turn back against the coast is a reader who flew past the post they
- // wanted and is hunting for it: the page is caught at once, and that notch
- // and the next few are short, quick steps (a quarter of a notch's travel,
- // settling fast) rather than a fresh flight the other way. Each notch on
- // in the same direction grows the step back towards a full one, so a few
- // flicks have the wheel flying again; a turn back while hunting starts the
- // small steps over, and a pause ends the hunt — the next notch is the
- // plain push from rest it has always been.
- const HUNT = [0.25, 0.35, 0.5, 0.7, 0.85]; // of a notch's travel, notch by notch
- const HUNT_TAU = 110; // ms: a hunting step settles quickly
- const HUNT_IDLE = 2000; // ms without a notch, and the hunt is over
- let hunt = -1, huntDir = 0, huntAt = 0; // the next HUNT entry while hunting
- const halt = () => { stop(); hunt = -1; };
+ // A turn back against the coast catches the page at once and is a plain
+ // notch from rest — no flight the other way, since the next couple of
+ // notches after a reversal (CALM) don't wind the speed up, so a couple of
+ // correcting notches travel a couple of notches; a spin that keeps going
+ // winds up again from the third. (A spell of quarter-size "hunting"
+ // notches ramping back up was tried here and felt like a stutter and a
+ // slow patch; a 700 ms spell without wind-up after a reversal was tried
+ // too and a spin begun inside it felt slow — Adam, 2026-10-16.)
+ const CALM = 2; // notches after a reversal without wind-up
+ let calmLeft = 0; // (lastDir/lastMoveAt: the tick's, above)
let smoothUntil = 0; // a trackpad's events were just seen: hands off
window.addEventListener('wheel', (e) => {
if (e.ctrlKey || e.shiftKey || e.defaultPrevented) return;
const notch = e.deltaMode === 1 || (e.wheelDeltaY && e.wheelDeltaY % 120 === 0 && !e.deltaX);
- if (!notch) { smoothUntil = e.timeStamp + 400; halt(); return; }
+ if (!notch) { smoothUntil = e.timeStamp + 400; stop(); return; }
if (e.timeStamp < smoothUntil || matchMedia('(pointer: coarse)').matches || !inScope(e.target)) return;
e.preventDefault();
const px = e.deltaMode === 1 ? e.deltaY * 40 : e.deltaY;
const dir = Math.sign(px);
if (!dir) return;
const now = e.timeStamp;
- if (hunt >= 0 && now - huntAt > HUNT_IDLE) hunt = -1;
- // the way the page is going: its coast, the coast that just died, or the hunt
- const going = v ? Math.sign(v) : now - lastMoveAt < 400 ? lastDir : hunt >= 0 ? huntDir : 0;
- if (going && dir !== going) { stop(); hunt = 0; huntDir = dir; }
- else if (hunt >= 0 && ++hunt >= HUNT.length) hunt = -1;
- if (hunt >= 0) {
+ // the way the page is going: its coast, or the coast that only just died
+ const going = v ? Math.sign(v) : now - lastMoveAt < 400 ? lastDir : 0;
+ if (going && dir !== going) {
// A reversal catches the old coast immediately. Discard its fractional
// step and frame clock too: after a busy frame, applying the NEW velocity
// to the OLD elapsed time made the first reverse step jump. Give the
- // notch a small response now, then a quick coast over the rest of its
- // (short) travel; quick flicks still add up.
- huntAt = now;
- const travel = px * 3 * HUNT[hunt];
- const first = dir * Math.min(16, Math.abs(travel / HUNT_TAU) * 16);
+ // notch a small response now, then coast the rest of its travel.
+ stop();
+ calmLeft = CALM;
+ const first = dir * Math.min(16, Math.abs(px * 3 / TAU) * 16);
window.scrollBy({ top: first, behavior: 'instant' });
lastDir = dir; lastMoveAt = now;
- coast(v + (travel - first) / HUNT_TAU, HUNT_TAU);
+ coast((px * 3 - first) / TAU, TAU);
} else {
// A notch into a page already moving pushes harder: the faster it
// goes, the more each turn adds (up to 2.5x), so keeping the wheel
// turning winds the speed up instead of settling at a stroll. From
// rest a notch is the same push as ever.
- const gain = 1 + Math.min(1.5, Math.abs(v) / 4);
+ const gain = calmLeft-- > 0 ? 1 : 1 + Math.min(1.5, Math.abs(v) / 4);
coast(v + (px * 3 * gain) / TAU, TAU);
}
}, { passive: false });
// anything else that moves the page takes over from a coast
- window.addEventListener('keydown', (e) => { if (/^(Arrow|Page|Home|End| )/.test(e.key)) halt(); });
- window.addEventListener('touchstart', halt, { passive: true });
+ window.addEventListener('keydown', (e) => { if (/^(Arrow|Page|Home|End| )/.test(e.key)) stop(); });
+ window.addEventListener('touchstart', stop, { passive: true });
// ---- grab and flick
let drag = null;
document.addEventListener('pointerdown', (e) => {
- if (raf) halt(); // a click catches a coasting page, like a finger does
+ if (raf) stop(); // a click catches a coasting page, like a finger does
if (e.pointerType !== 'mouse' || e.button !== 0 || !inScope(e.target)) return;
drag = { x: e.clientX, y: e.clientY, ly: e.clientY, on: false, pts: [] };
});