From d6a3d02189a2673adffb14714f89c6763361d6ea Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Tue, 31 Dec 2024 16:43:46 +0100 Subject: [PATCH] Fix page jumping when scrolling continuous pagers Both hardcoded thresholds (MIN_VISIBLE_PX 0 or 1) break the "first visible image" detection in different kind of ways. With 1 it always incorrectly jumps to the next page when resuming a chapter, since the page to resume will bet at 0px. With 0 it fails to detect the correct first visible image when scrolling (automatically not via wheel or touch) to another page. This happens when there is no gap between the pages because in this case the start of the current image and the end of the previous image are at the same coordinate. Thus, the previous image is still incorrectly detected as visible since the end still aligns with the viewports start/end --- src/modules/reader/services/ReaderControls.ts | 18 +++++++++- .../reader/utils/ReaderPager.utils.tsx | 35 ++++++++++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/modules/reader/services/ReaderControls.ts b/src/modules/reader/services/ReaderControls.ts index fc6d30b6..ef3b4b4a 100644 --- a/src/modules/reader/services/ReaderControls.ts +++ b/src/modules/reader/services/ReaderControls.ts @@ -445,7 +445,23 @@ export class ReaderControls { type: PageInViewportType, readingDirection: ReadingDirection, ) { - const firstVisibleImageIndex = imageRefs.current.findIndex((image) => image && isPageInViewport(image, type)); + const themeDirectionForReadingDirection = READING_DIRECTION_TO_THEME_DIRECTION[readingDirection]; + + const firstVisibleImageIndex = imageRefs.current.findIndex( + (image) => + image && + isPageInViewport( + image, + type, + // the pages aren't always properly scrolled to the top which results in the leading page to still be slightly + // visible (e.g. 0.0123px) breaking the "first visible image" detection + { + bottom: 1, + left: getOptionForDirection(0, window.innerWidth + 1, themeDirectionForReadingDirection), + right: getOptionForDirection(1, 0, themeDirectionForReadingDirection), + }, + ), + ); const lastPage = imageRefs.current?.[imageRefs.current.length - 1]; const isEndReached = lastPage && isEndOfPageInViewport(lastPage, type, readingDirection); diff --git a/src/modules/reader/utils/ReaderPager.utils.tsx b/src/modules/reader/utils/ReaderPager.utils.tsx index f098fab7..e615a2ae 100644 --- a/src/modules/reader/utils/ReaderPager.utils.tsx +++ b/src/modules/reader/utils/ReaderPager.utils.tsx @@ -291,8 +291,15 @@ export const createReaderPage = ( /> ); +type InViewportThresholds = { + top?: number; + bottom?: number; + left?: number; + right?: number; +}; const getIsPageInViewportInfo = ( element: HTMLElement, + argThresholds?: InViewportThresholds, ): { isLeftInViewport: boolean; isRightInViewport: boolean; @@ -303,15 +310,21 @@ const getIsPageInViewportInfo = ( } => { const { top, bottom, left, right } = element.getBoundingClientRect(); - const MIN_VISIBLE_PX = 0; + const thresholds = { + top: 0, + bottom: 0, + left: 0, + right: 0, + ...argThresholds, + }; - const isLeftInViewport = left >= MIN_VISIBLE_PX && left <= window.innerWidth; - const isRightInViewport = right >= MIN_VISIBLE_PX && right <= window.innerWidth; - const isFillingWidthViewportCompletely = left <= MIN_VISIBLE_PX && right >= window.innerWidth; + const isLeftInViewport = left >= thresholds.left && left <= window.innerWidth; + const isRightInViewport = right >= thresholds.right && right <= window.innerWidth; + const isFillingWidthViewportCompletely = left <= thresholds.left && right >= window.innerWidth; - const isTopInViewport = top >= MIN_VISIBLE_PX && top <= window.innerHeight; - const isBottomInViewport = bottom >= MIN_VISIBLE_PX && bottom <= window.innerHeight; - const isFillingHeightViewportCompletely = top <= MIN_VISIBLE_PX && bottom >= window.innerHeight; + const isTopInViewport = top >= thresholds.top && top <= window.innerHeight; + const isBottomInViewport = bottom >= thresholds.bottom && bottom <= window.innerHeight; + const isFillingHeightViewportCompletely = top <= thresholds.top && bottom >= window.innerHeight; return { isLeftInViewport, @@ -323,7 +336,11 @@ const getIsPageInViewportInfo = ( }; }; -export const isPageInViewport = (element: HTMLElement, type: PageInViewportType): boolean => { +export const isPageInViewport = ( + element: HTMLElement, + type: PageInViewportType, + thresholds?: InViewportThresholds, +): boolean => { const { isLeftInViewport, isRightInViewport, @@ -331,7 +348,7 @@ export const isPageInViewport = (element: HTMLElement, type: PageInViewportType) isTopInViewport, isBottomInViewport, isFillingHeightViewportCompletely, - } = getIsPageInViewportInfo(element); + } = getIsPageInViewportInfo(element, thresholds); const isInViewportX = isLeftInViewport || isRightInViewport || isFillingWidthViewportCompletely; const isInViewportY = isTopInViewport || isBottomInViewport || isFillingHeightViewportCompletely;