Improve reader end of last page reached detection

#1116
This commit is contained in:
schroda
2026-06-16 18:03:17 +02:00
parent eef5f762cb
commit dbf8500709
2 changed files with 51 additions and 31 deletions

View File

@@ -528,18 +528,24 @@ class ReaderControlsClass {
(image) => (image) =>
image && image &&
isPageInViewport(image, type, { isPageInViewport(image, type, {
thresholds: { bounds: {
bottom: 1, bottom: {
left: getOptionForDirection( min: 1,
0, },
pageHorizontalEndInViewportThreshold, left: {
themeDirectionOfReadingDirection, min: getOptionForDirection(
), 0,
right: getOptionForDirection( pageHorizontalEndInViewportThreshold,
pageHorizontalEndInViewportThreshold, themeDirectionOfReadingDirection,
0, ),
themeDirectionOfReadingDirection, },
), right: {
min: getOptionForDirection(
pageHorizontalEndInViewportThreshold,
0,
themeDirectionOfReadingDirection,
),
},
}, },
}), }),
); );

View File

@@ -31,6 +31,7 @@ import { getOptionForDirection } from '@/features/theme/services/ThemeCreator.ts
import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/features/reader/settings/ReaderSettings.constants.tsx'; import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/features/reader/settings/ReaderSettings.constants.tsx';
import { coerceIn } from '@/lib/HelperFunctions.ts'; import { coerceIn } from '@/lib/HelperFunctions.ts';
import type { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts'; import type { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts';
import merge from 'lodash/fp/merge';
type CSSObject = ReturnType<Theme['applyStyles']>; type CSSObject = ReturnType<Theme['applyStyles']>;
@@ -331,11 +332,17 @@ export const createReaderPage = (
/> />
); );
type InViewportThresholds = { type InViewportBound = {
top?: number; min?: number;
bottom?: number; max?: number;
left?: number; threshold?: number;
right?: number; };
type InViewportBounds = {
top?: InViewportBound;
bottom?: InViewportBound;
left?: InViewportBound;
right?: InViewportBound;
}; };
type InViewportOptions = { type InViewportOptions = {
/** /**
@@ -344,14 +351,14 @@ type InViewportOptions = {
*/ */
truncateValues?: boolean; truncateValues?: boolean;
/** /**
* Thresholds are not considered for the detection of an image filling the whole viewport. * Bounds are not considered for the detection of an image filling the whole viewport.
* They are only used for detecting if a specific side of an image is inside the viewport * They are only used for detecting if a specific side of an image is inside the viewport
*/ */
thresholds?: InViewportThresholds; bounds?: InViewportBounds;
}; };
const getIsPageInViewportInfo = ( const getIsPageInViewportInfo = (
element: HTMLElement, element: HTMLElement,
{ truncateValues, thresholds: argThresholds }: InViewportOptions = { truncateValues: true, thresholds: {} }, { truncateValues, bounds: argBounds }: InViewportOptions = { truncateValues: true, bounds: {} },
): { ): {
isLeftInViewport: boolean; isLeftInViewport: boolean;
isRightInViewport: boolean; isRightInViewport: boolean;
@@ -368,20 +375,27 @@ const getIsPageInViewportInfo = (
const left = maybeTruncateValue(leftRaw); const left = maybeTruncateValue(leftRaw);
const right = maybeTruncateValue(rightRaw); const right = maybeTruncateValue(rightRaw);
const thresholds = { const THRESHOLD = 1;
top: 0, const bounds = merge(
bottom: 0, {
left: 0, top: { min: 0, max: window.innerHeight + THRESHOLD, threshold: THRESHOLD },
right: 0, bottom: { min: 0, max: window.innerHeight + THRESHOLD, threshold: THRESHOLD },
...argThresholds, left: { min: 0, max: window.innerWidth + THRESHOLD, threshold: THRESHOLD },
}; right: { min: 0, max: window.innerWidth + THRESHOLD, threshold: THRESHOLD },
} satisfies InViewportBounds,
argBounds,
);
const isLeftInViewport = left >= thresholds.left && left <= window.innerWidth; // oxlint-disable-next-line unicorn/consistent-function-scoping
const isRightInViewport = right >= thresholds.right && right <= window.innerWidth; const meetsBounds = (value: number, viewportValue: number, { min, max, threshold }: Required<InViewportBound>) =>
value >= min && value <= max && value <= viewportValue + threshold;
const isLeftInViewport = meetsBounds(left, window.innerWidth, bounds.left);
const isRightInViewport = meetsBounds(right, window.innerWidth, bounds.right);
const isFillingWidthViewportCompletely = left <= 0 && right >= window.innerWidth; const isFillingWidthViewportCompletely = left <= 0 && right >= window.innerWidth;
const isTopInViewport = top >= thresholds.top && top <= window.innerHeight; const isTopInViewport = meetsBounds(top, window.innerHeight, bounds.top);
const isBottomInViewport = bottom >= thresholds.bottom && bottom <= window.innerHeight; const isBottomInViewport = meetsBounds(bottom, window.innerHeight, bounds.bottom);
const isFillingHeightViewportCompletely = top <= 0 && bottom >= window.innerHeight; const isFillingHeightViewportCompletely = top <= 0 && bottom >= window.innerHeight;
return { return {