feat(paginator): manual touch scrolling in scrolled layouts — kill the native fling

Scrolled (webtoon) layouts now scroll single-finger drags manually, the
same path paginated layouts always used: the browser's native touch
scrolling — and its fling momentum — is suppressed so the full gesture
stream stays observable to hosts. The reader's chrome UX (bookhoard
handoff item 7) classifies vertical flicks from that stream: flick-down
reveals, down/up dismisses. Side-swipes in paginated layouts and drags
in scrolled ones both page/scroll exactly as before, minus momentum.

Release behavior in scrolled layouts is stop-where-released — no snap,
deliberately: webtoon panels don't align to screen boundaries and a
snap would fight zoomed panning. Long jumps stay the position UI's
job. Pinch zoom, multi-touch, and active touch selection keep their
native behavior via the existing guards (they return before the
preventDefault). Desktop wheel scrolling is untouched.

Version 0.5.4.
This commit is contained in:
John O'Keefe committed 2026-10-15 13:29:30 -04:00
1 parent 7796b05582
commit f622841e29
2 files changed
+13 -3

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "foliate-js", "name": "foliate-js",
"version": "0.5.3", "version": "0.5.4",
"description": "Render e-books in the browser", "description": "Render e-books in the browser",
"repository": { "repository": {
"type": "git", "type": "git",
+12 -2
View File
@@ -986,7 +986,14 @@ export class Paginator extends HTMLElement {
const state = this.#touchState const state = this.#touchState
if (state.pinched) return if (state.pinched) return
state.pinched = globalThis.visualViewport.scale > 1 state.pinched = globalThis.visualViewport.scale > 1
if (this.scrolled || state.pinched) return // Scrolled layouts (webtoon) scroll MANUALLY, like paginated ones:
// the browser's native touch scrolling — and its fling momentum —
// is suppressed so the full gesture stream stays observable to
// hosts (the reader classifies vertical flicks as chrome-reveal).
// Momentum is deliberately absent: drags are 1:1 and stop dead on
// release (no snap — webtoon panels don't align to screen
// boundaries); long jumps go through the reader's position UI.
if (state.pinched) return
if (this.#touchSelActive) return if (this.#touchSelActive) return
if (e.touches.length > 1) { if (e.touches.length > 1) {
if (this.#touchScrolled) e.preventDefault() if (this.#touchScrolled) e.preventDefault()
@@ -1008,11 +1015,14 @@ export class Paginator extends HTMLElement {
#onTouchEnd() { #onTouchEnd() {
const wasScrolled = this.#touchScrolled const wasScrolled = this.#touchScrolled
this.#touchScrolled = false this.#touchScrolled = false
if (this.scrolled || this.#touchSelActive) return if (this.#touchSelActive) return
// A touch that never panned (a tap) is already page-aligned; // A touch that never panned (a tap) is already page-aligned;
// snapping anyway emits a no-op relocate, which readers treat // snapping anyway emits a no-op relocate, which readers treat
// as a page turn (e.g. dismissing a just-opened popover). // as a page turn (e.g. dismissing a just-opened popover).
if (!wasScrolled) return if (!wasScrolled) return
// No velocity paging in scrolled layouts: content stays exactly
// where released.
if (this.scrolled) return
// XXX: Firefox seems to report scale as 1... sometimes...? // XXX: Firefox seems to report scale as 1... sometimes...?
// at this point I'm basically throwing `requestAnimationFrame` at // at this point I'm basically throwing `requestAnimationFrame` at