Preserve scroll position continuous pager on previous chapter load
In the horizontal pager loading the previous chapter caused the scroll position to get lost and instead of starting at the end of the previous chapter, the scroll position was at the very start. This happened everytime no matter if the scroll position was at the x 0 or not before the previous chapter got visible. Same issue can be observed in the vertical pager in case the scroll position is at y 0 before the previous chapter has been rendered.
This commit is contained in:
@@ -59,12 +59,14 @@ import { useReaderHandlePageSelection } from '@/modules/reader/hooks/useReaderHa
|
||||
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
|
||||
import { ReaderChapterViewer } from '@/modules/reader/components/viewer/ReaderChapterViewer.tsx';
|
||||
import {
|
||||
getPreviousNextChapterVisibility,
|
||||
getReaderChapterViewerCurrentPageIndex,
|
||||
getReaderChapterViewResumeMode,
|
||||
} from '@/modules/reader/utils/Reader.utils.ts';
|
||||
import { coerceIn, noOp } from '@/lib/HelperFunctions.ts';
|
||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||
import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts';
|
||||
import { useReaderPreserveScrollPosition } from '@/modules/reader/hooks/useReaderPreserveScrollPosition.ts';
|
||||
|
||||
const READING_MODE_TO_IN_VIEWPORT_TYPE: Record<ReadingMode, PageInViewportType> = {
|
||||
[ReadingMode.SINGLE_PAGE]: PageInViewportType.X,
|
||||
@@ -254,6 +256,15 @@ const BaseReaderViewer = forwardRef(
|
||||
scrollElementRef,
|
||||
);
|
||||
useReaderAutoScroll(isOverlayVisible, automaticScrolling);
|
||||
useReaderPreserveScrollPosition(
|
||||
scrollElementRef,
|
||||
currentChapter?.id,
|
||||
currentChapterIndex,
|
||||
chaptersToRender,
|
||||
visibleChapters,
|
||||
isContinuousReadingModeActive,
|
||||
readingDirection,
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
chapterViewerSize.current = { minChapterViewWidth: 0, minChapterViewHeight: 0 };
|
||||
@@ -321,17 +332,11 @@ const BaseReaderViewer = forwardRef(
|
||||
(isLastLeadingChapter && visibleChapters.isLeadingChapterPreloadMode) ||
|
||||
(isLastTrailingChapter && visibleChapters.isTrailingChapterPreloadMode);
|
||||
|
||||
const isPreviousChapterLoaded = !!chaptersToRender[chapterIndex + 1];
|
||||
const isPreviousChapterLastLeadingChapter = chapterIndex + 1 >= chaptersToRender.length - 1;
|
||||
const isPreviousChapterPreloading =
|
||||
isPreviousChapterLastLeadingChapter && visibleChapters.isLeadingChapterPreloadMode;
|
||||
const isPreviousChapterVisible = isPreviousChapterLoaded && !isPreviousChapterPreloading;
|
||||
|
||||
const isNextChapterLoaded = !!chaptersToRender[chapterIndex - 1];
|
||||
const isNextChapterLastTrailingChapter = chapterIndex - 1 < 0;
|
||||
const isNextChapterPreloading =
|
||||
isNextChapterLastTrailingChapter && visibleChapters.isTrailingChapterPreloadMode;
|
||||
const isNextChapterVisible = isNextChapterLoaded && !isNextChapterPreloading;
|
||||
const previousNextChapterVisibility = getPreviousNextChapterVisibility(
|
||||
chapterIndex,
|
||||
chaptersToRender,
|
||||
visibleChapters,
|
||||
);
|
||||
|
||||
return (
|
||||
<ReaderChapterViewer
|
||||
@@ -339,8 +344,8 @@ const BaseReaderViewer = forwardRef(
|
||||
chapterId={chapter.id}
|
||||
previousChapterId={previousChapter?.id}
|
||||
nextChapterId={nextChapter?.id}
|
||||
isPreviousChapterVisible={isPreviousChapterVisible}
|
||||
isNextChapterVisible={isNextChapterVisible}
|
||||
isPreviousChapterVisible={previousNextChapterVisibility.previous}
|
||||
isNextChapterVisible={previousNextChapterVisibility.next}
|
||||
lastPageRead={coerceIn(chapter.lastPageRead, 0, chapter.pageCount - 1)}
|
||||
currentPageIndex={getReaderChapterViewerCurrentPageIndex(
|
||||
currentPageIndex,
|
||||
|
||||
94
src/modules/reader/hooks/useReaderPreserveScrollPosition.ts
Normal file
94
src/modules/reader/hooks/useReaderPreserveScrollPosition.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { ChapterIdInfo } from '@/modules/chapter/services/Chapters.ts';
|
||||
import { ReaderStateChapters, ReadingDirection } from '@/modules/reader/types/Reader.types.ts';
|
||||
import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts';
|
||||
import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||
import { getPreviousNextChapterVisibility } from '@/modules/reader/utils/Reader.utils.ts';
|
||||
import { TChapterReader } from '@/modules/chapter/Chapter.types.ts';
|
||||
|
||||
export const useReaderPreserveScrollPosition = (
|
||||
scrollElementRef: RefObject<HTMLElement | null>,
|
||||
currentChapterId: ChapterIdInfo['id'] | undefined,
|
||||
chapterIndex: number,
|
||||
chaptersToRender: TChapterReader[],
|
||||
visibleChapters: ReaderStateChapters['visibleChapters'],
|
||||
isContinuousReadingModeActive: boolean,
|
||||
readingDirection: ReadingDirection,
|
||||
) => {
|
||||
const scrollPosition = useRef({ left: 0, top: 0, scrollWidth: 0, scrollHeight: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const element = scrollElementRef.current;
|
||||
|
||||
if (!element) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const onScroll = () => {
|
||||
scrollPosition.current = {
|
||||
...scrollPosition.current,
|
||||
left: element.scrollLeft,
|
||||
top: element.scrollTop,
|
||||
};
|
||||
};
|
||||
|
||||
element.addEventListener('scroll', onScroll);
|
||||
|
||||
return () => element.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const scrollElement = scrollElementRef.current;
|
||||
const { left, top, scrollWidth, scrollHeight } = scrollPosition.current;
|
||||
|
||||
if (!scrollElement || !isContinuousReadingModeActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPosition.current = {
|
||||
...scrollPosition.current,
|
||||
scrollWidth: scrollElement.scrollWidth,
|
||||
scrollHeight: scrollElement.scrollHeight,
|
||||
};
|
||||
|
||||
const themeDirectionForReadingDirection = READING_DIRECTION_TO_THEME_DIRECTION[readingDirection];
|
||||
|
||||
const previousNextChapterVisibility = getPreviousNextChapterVisibility(
|
||||
chapterIndex,
|
||||
chaptersToRender,
|
||||
visibleChapters,
|
||||
);
|
||||
|
||||
const wasScrolledBackwardHorizontal = Math.abs(left) < window.innerWidth * 1.5;
|
||||
const wasScrolledBackwardVertical = top < window.innerHeight * 1.5;
|
||||
|
||||
const widthOfPrependedContent = scrollElement.scrollWidth - scrollWidth;
|
||||
const heightOfPrependedContent = scrollElement.scrollHeight - scrollHeight;
|
||||
|
||||
const newLeft = wasScrolledBackwardHorizontal
|
||||
? getOptionForDirection(
|
||||
widthOfPrependedContent,
|
||||
-widthOfPrependedContent,
|
||||
themeDirectionForReadingDirection,
|
||||
) + left
|
||||
: left;
|
||||
const newTop = wasScrolledBackwardVertical ? heightOfPrependedContent + top : top;
|
||||
|
||||
// only relevant when prepending content to the dom due to the resulting layout shift
|
||||
const isFirstRenderOfPreviousChapter =
|
||||
!previousNextChapterVisibility.previous && (wasScrolledBackwardHorizontal || wasScrolledBackwardVertical);
|
||||
if (!isFirstRenderOfPreviousChapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollElement.scrollTo(newLeft, newTop);
|
||||
}, [currentChapterId]);
|
||||
};
|
||||
@@ -321,3 +321,25 @@ export const getReaderChapterViewResumeMode = (
|
||||
|
||||
return ReaderResumeMode.START;
|
||||
};
|
||||
|
||||
export const getPreviousNextChapterVisibility = (
|
||||
chapterIndex: number,
|
||||
chaptersToRender: TChapterReader[],
|
||||
visibleChapters: ReaderStateChapters['visibleChapters'],
|
||||
): { previous: boolean; next: boolean } => {
|
||||
const isPreviousChapterLoaded = !!chaptersToRender[chapterIndex + 1];
|
||||
const isPreviousChapterLastLeadingChapter = chapterIndex + 1 >= chaptersToRender.length - 1;
|
||||
const isPreviousChapterPreloading =
|
||||
isPreviousChapterLastLeadingChapter && visibleChapters.isLeadingChapterPreloadMode;
|
||||
const isPreviousChapterVisible = isPreviousChapterLoaded && !isPreviousChapterPreloading;
|
||||
|
||||
const isNextChapterLoaded = !!chaptersToRender[chapterIndex - 1];
|
||||
const isNextChapterLastTrailingChapter = chapterIndex - 1 < 0;
|
||||
const isNextChapterPreloading = isNextChapterLastTrailingChapter && visibleChapters.isTrailingChapterPreloadMode;
|
||||
const isNextChapterVisible = isNextChapterLoaded && !isNextChapterPreloading;
|
||||
|
||||
return {
|
||||
previous: isPreviousChapterVisible,
|
||||
next: isNextChapterVisible,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user