Preserve scroll position during infinite scroll initial previous chapter render
This commit is contained in:
@@ -284,7 +284,11 @@ const BaseReaderViewer = forwardRef(
|
|||||||
useReaderAutoScroll(isOverlayVisible, automaticScrolling);
|
useReaderAutoScroll(isOverlayVisible, automaticScrolling);
|
||||||
useReaderPreserveScrollPosition(
|
useReaderPreserveScrollPosition(
|
||||||
scrollElementRef,
|
scrollElementRef,
|
||||||
|
currentChapter?.id,
|
||||||
|
currentChapterIndex,
|
||||||
currentPageIndex,
|
currentPageIndex,
|
||||||
|
chaptersToRender,
|
||||||
|
visibleChapters,
|
||||||
readingMode,
|
readingMode,
|
||||||
readingDirection,
|
readingDirection,
|
||||||
setPageToScrollToIndex,
|
setPageToScrollToIndex,
|
||||||
|
|||||||
@@ -7,7 +7,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react';
|
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react';
|
||||||
import { ReaderPageScaleMode, ReadingDirection, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
import {
|
||||||
|
ReaderPageScaleMode,
|
||||||
|
ReaderStateChapters,
|
||||||
|
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,
|
isContinuousReadingMode,
|
||||||
@@ -15,6 +20,9 @@ import {
|
|||||||
isHeightPageScaleMode,
|
isHeightPageScaleMode,
|
||||||
isReaderWidthEditable,
|
isReaderWidthEditable,
|
||||||
} from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
} from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
||||||
|
import { getPreviousNextChapterVisibility } from '@/modules/reader/utils/Reader.utils.ts';
|
||||||
|
import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
||||||
|
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
|
||||||
|
|
||||||
const shouldPreserveOnResizeChange = (
|
const shouldPreserveOnResizeChange = (
|
||||||
readingMode: ReadingMode,
|
readingMode: ReadingMode,
|
||||||
@@ -238,16 +246,119 @@ const usePreserveOnLeadingPageRender = (scrollElementRef: RefObject<HTMLElement
|
|||||||
}, [isContinuousReadingModeActive, isContinuousVerticalReadingModeActive]);
|
}, [isContinuousReadingModeActive, isContinuousVerticalReadingModeActive]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const usePreserveOnInfiniteScrollPreviousChapterInitialRender = (
|
||||||
|
scrollElementRef: RefObject<HTMLElement | null>,
|
||||||
|
currentChapterId: ChapterIdInfo['id'] | undefined,
|
||||||
|
currentChapterIndex: number,
|
||||||
|
currentPageIndex: number,
|
||||||
|
chaptersToRender: TChapterReader[],
|
||||||
|
visibleChapters: ReaderStateChapters['visibleChapters'],
|
||||||
|
isContinuousReadingModeActive: boolean,
|
||||||
|
) => {
|
||||||
|
const preservationDataRef = useScrollPreservationData(scrollElementRef);
|
||||||
|
|
||||||
|
const preserveScrollPosition = (): boolean => {
|
||||||
|
const scrollElement = scrollElementRef.current;
|
||||||
|
const { left, top, visibleElementLeft, visibleElementTop, visibleElement } = preservationDataRef.current;
|
||||||
|
|
||||||
|
if (!scrollElement || !isContinuousReadingModeActive || !visibleElement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousNextChapterVisibility = getPreviousNextChapterVisibility(
|
||||||
|
currentChapterIndex,
|
||||||
|
chaptersToRender,
|
||||||
|
visibleChapters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRenderOfPreviousChapter = currentPageIndex === 0;
|
||||||
|
|
||||||
|
// only relevant when prepending content to the dom due to the resulting layout shift
|
||||||
|
const isFirstRenderOfPreviousChapter = !previousNextChapterVisibility.previous && isRenderOfPreviousChapter;
|
||||||
|
if (!isFirstRenderOfPreviousChapter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newLeft = left - visibleElementLeft + visibleElement.offsetLeft;
|
||||||
|
const newTop = top - visibleElementTop + visibleElement.offsetTop;
|
||||||
|
|
||||||
|
scrollElement.scrollTo(newLeft, newTop);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const scrollElement = scrollElementRef.current;
|
||||||
|
if (!scrollElement || !isContinuousReadingModeActive) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let preservedScrollPosition = false;
|
||||||
|
|
||||||
|
const updateObservation = (nodes: NodeList, resizeAction: (element: Element) => void) => {
|
||||||
|
Array.from(nodes)
|
||||||
|
.filter((node) => node instanceof HTMLElement)
|
||||||
|
.flatMap((element) =>
|
||||||
|
element instanceof HTMLImageElement ? [element] : Array.from(element.querySelectorAll('img')),
|
||||||
|
)
|
||||||
|
.forEach(resizeAction);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
if (preservedScrollPosition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
preservedScrollPosition = preserveScrollPosition();
|
||||||
|
if (preservedScrollPosition) {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const mutationObserver = new MutationObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
updateObservation(entry.addedNodes, (element) => resizeObserver.observe(element));
|
||||||
|
updateObservation(entry.removedNodes, (element) => resizeObserver.unobserve(element));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mutationObserver.observe(scrollElement, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mutationObserver.disconnect();
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [currentChapterId]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
preserveScrollPosition();
|
||||||
|
}, [currentChapterId]);
|
||||||
|
};
|
||||||
|
|
||||||
export const useReaderPreserveScrollPosition = (
|
export const useReaderPreserveScrollPosition = (
|
||||||
scrollElementRef: RefObject<HTMLElement | null>,
|
scrollElementRef: RefObject<HTMLElement | null>,
|
||||||
pageIndex: number,
|
currentChapterId: ChapterIdInfo['id'] | undefined,
|
||||||
|
currentChapterIndex: number,
|
||||||
|
currentPageIndex: number,
|
||||||
|
chaptersToRender: TChapterReader[],
|
||||||
|
visibleChapters: ReaderStateChapters['visibleChapters'],
|
||||||
readingMode: ReadingMode,
|
readingMode: ReadingMode,
|
||||||
readingDirection: ReadingDirection,
|
readingDirection: ReadingDirection,
|
||||||
setPageToScrollToIndex: ReaderStatePages['setPageToScrollToIndex'],
|
setPageToScrollToIndex: ReaderStatePages['setPageToScrollToIndex'],
|
||||||
pageScaleMode: ReaderPageScaleMode,
|
pageScaleMode: ReaderPageScaleMode,
|
||||||
) => {
|
) => {
|
||||||
|
usePreserveOnInfiniteScrollPreviousChapterInitialRender(
|
||||||
|
scrollElementRef,
|
||||||
|
currentChapterId,
|
||||||
|
currentChapterIndex,
|
||||||
|
currentPageIndex,
|
||||||
|
chaptersToRender,
|
||||||
|
visibleChapters,
|
||||||
|
isContinuousReadingMode(readingMode),
|
||||||
|
);
|
||||||
usePreserveOnLeadingPageRender(scrollElementRef, readingMode);
|
usePreserveOnLeadingPageRender(scrollElementRef, readingMode);
|
||||||
usePreserveOnWindowResize(readingMode, pageScaleMode, setPageToScrollToIndex, pageIndex);
|
usePreserveOnWindowResize(readingMode, pageScaleMode, setPageToScrollToIndex, currentPageIndex);
|
||||||
usePreserveOnValueChange(readingDirection, pageIndex, setPageToScrollToIndex);
|
usePreserveOnValueChange(readingDirection, currentPageIndex, setPageToScrollToIndex);
|
||||||
usePreserveOnValueChange(readingMode, pageIndex, setPageToScrollToIndex);
|
usePreserveOnValueChange(readingMode, currentPageIndex, setPageToScrollToIndex);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user