Ignore trailing image resize for scroll preservation

Resizes of elements below the current scroll position do not cause a layout shift; thus, they can be ignored.

This was a problem when using tap zone click scrolling, because the scrolling was aborted by the scroll position preservation
This commit is contained in:
schroda
2025-05-02 14:52:25 +02:00
parent 0c4e772542
commit d606130846
2 changed files with 40 additions and 16 deletions

View File

@@ -287,7 +287,6 @@ const BaseReaderViewer = forwardRef(
currentPageIndex, currentPageIndex,
readingMode, readingMode,
readingDirection, readingDirection,
isContinuousReadingModeActive,
setPageToScrollToIndex, setPageToScrollToIndex,
pageScaleMode, pageScaleMode,
); );

View File

@@ -10,6 +10,7 @@ import { RefObject, useEffect, useLayoutEffect, useRef } from 'react';
import { ReaderPageScaleMode, ReadingDirection, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; import { ReaderPageScaleMode, ReadingDirection, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types'; import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types';
import { import {
isContinuousReadingMode,
isContinuousVerticalReadingMode, isContinuousVerticalReadingMode,
isHeightPageScaleMode, isHeightPageScaleMode,
isReaderWidthEditable, isReaderWidthEditable,
@@ -72,10 +73,7 @@ const usePreserveOnWindowResize = (
}, [readingMode, pageScaleMode, pageIndex]); }, [readingMode, pageScaleMode, pageIndex]);
}; };
const usePreserveOnReaderViewerElementMutation = ( const usePreserveOnLeadingPageRender = (scrollElementRef: RefObject<HTMLElement | null>, readingMode: ReadingMode) => {
scrollElementRef: RefObject<HTMLElement | null>,
isContinuousReadingModeActive: boolean,
) => {
const scrollPosition = useRef<{ const scrollPosition = useRef<{
left: number; left: number;
top: number; top: number;
@@ -90,6 +88,9 @@ const usePreserveOnReaderViewerElementMutation = (
visibleElementTop: 0, visibleElementTop: 0,
}); });
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode);
const isContinuousVerticalReadingModeActive = isContinuousVerticalReadingMode(readingMode);
useEffect(() => { useEffect(() => {
const scrollElement = scrollElementRef.current; const scrollElement = scrollElementRef.current;
@@ -113,20 +114,42 @@ const usePreserveOnReaderViewerElementMutation = (
return () => scrollElement.removeEventListener('scroll', onScroll); return () => scrollElement.removeEventListener('scroll', onScroll);
}, []); }, []);
useLayoutEffect(() => { useEffect(() => {
const scrollElement = scrollElementRef.current; const scrollElement = scrollElementRef.current;
if (!scrollElement || !isContinuousReadingModeActive) { if (!scrollElement || !isContinuousReadingModeActive) {
return () => {}; return () => {};
} }
const preserveScrollPosition = () => { const preserveScrollPosition: ResizeObserverCallback = (entries) => {
const { left, top, visibleElement, visibleElementLeft, visibleElementTop } = scrollPosition.current; const { left, top, visibleElement, visibleElementLeft, visibleElementTop } = scrollPosition.current;
if (!visibleElement) { if (!visibleElement) {
return; return;
} }
const entriesBeforeScrollPosition = entries.filter((entry) => {
if (!(entry.target instanceof HTMLElement)) {
return false;
}
const isPreloadPage = !entry.target.clientWidth && !entry.target.clientHeight;
if (isPreloadPage) {
return false;
}
if (isContinuousVerticalReadingModeActive) {
return entry.target.offsetTop < top;
}
return Math.abs(entry.target.offsetLeft) < Math.abs(left);
});
const includesElementsBeforeScrollPosition = !!entriesBeforeScrollPosition.length;
if (!includesElementsBeforeScrollPosition) {
return;
}
const newLeft = left - visibleElementLeft + visibleElement.offsetLeft; const newLeft = left - visibleElementLeft + visibleElement.offsetLeft;
const newTop = top - visibleElementTop + visibleElement.offsetTop; const newTop = top - visibleElementTop + visibleElement.offsetTop;
@@ -140,11 +163,13 @@ const usePreserveOnReaderViewerElementMutation = (
) => ) =>
Array.from(nodes) Array.from(nodes)
.filter((node) => node instanceof HTMLElement) .filter((node) => node instanceof HTMLElement)
.flatMap((element) => { .flatMap((element) =>
element instanceof HTMLImageElement ? [element] : Array.from(element.querySelectorAll('img')),
)
.forEach((element) => {
resizeAction(element); resizeAction(element);
return Array.from(element.querySelectorAll('img')); intersectionAction(element);
}) });
.forEach(intersectionAction);
const resizeObserver = new ResizeObserver(preserveScrollPosition); const resizeObserver = new ResizeObserver(preserveScrollPosition);
const intersectionObserver = new IntersectionObserver((entries) => { const intersectionObserver = new IntersectionObserver((entries) => {
@@ -165,8 +190,8 @@ const usePreserveOnReaderViewerElementMutation = (
for (const entry of entries) { for (const entry of entries) {
updateObservation( updateObservation(
entry.addedNodes, entry.addedNodes,
(n) => resizeObserver.observe(n), (element) => resizeObserver.observe(element),
(n) => intersectionObserver.observe(n), (element) => intersectionObserver.observe(element),
); );
updateObservation( updateObservation(
entry.removedNodes, entry.removedNodes,
@@ -178,6 +203,7 @@ const usePreserveOnReaderViewerElementMutation = (
mutationObserver.observe(scrollElement, { mutationObserver.observe(scrollElement, {
childList: true, childList: true,
subtree: true,
}); });
return () => { return () => {
@@ -185,7 +211,7 @@ const usePreserveOnReaderViewerElementMutation = (
resizeObserver.disconnect(); resizeObserver.disconnect();
intersectionObserver.disconnect(); intersectionObserver.disconnect();
}; };
}, [isContinuousReadingModeActive]); }, [isContinuousReadingModeActive, isContinuousVerticalReadingModeActive]);
}; };
export const useReaderPreserveScrollPosition = ( export const useReaderPreserveScrollPosition = (
@@ -193,11 +219,10 @@ export const useReaderPreserveScrollPosition = (
pageIndex: number, pageIndex: number,
readingMode: ReadingMode, readingMode: ReadingMode,
readingDirection: ReadingDirection, readingDirection: ReadingDirection,
isContinuousReadingModeActive: boolean,
setPageToScrollToIndex: ReaderStatePages['setPageToScrollToIndex'], setPageToScrollToIndex: ReaderStatePages['setPageToScrollToIndex'],
pageScaleMode: ReaderPageScaleMode, pageScaleMode: ReaderPageScaleMode,
) => { ) => {
usePreserveOnReaderViewerElementMutation(scrollElementRef, isContinuousReadingModeActive); usePreserveOnLeadingPageRender(scrollElementRef, readingMode);
usePreserveOnWindowResize(readingMode, pageScaleMode, setPageToScrollToIndex, pageIndex); usePreserveOnWindowResize(readingMode, pageScaleMode, setPageToScrollToIndex, pageIndex);
usePreserveOnValueChange(readingDirection, pageIndex, setPageToScrollToIndex); usePreserveOnValueChange(readingDirection, pageIndex, setPageToScrollToIndex);
usePreserveOnValueChange(readingMode, pageIndex, setPageToScrollToIndex); usePreserveOnValueChange(readingMode, pageIndex, setPageToScrollToIndex);