From 83ce1caee631e73de16d2105b909d8c0bb9d3d5a Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Mon, 16 Sep 2024 22:24:08 +0200 Subject: [PATCH] Prevent blinking on page switch in double mode --- src/components/reader/Page.tsx | 5 +- .../reader/pager/DoublePagedPager.tsx | 161 ++++++++++-------- 2 files changed, 89 insertions(+), 77 deletions(-) diff --git a/src/components/reader/Page.tsx b/src/components/reader/Page.tsx index 8e5f0725..168052e1 100644 --- a/src/components/reader/Page.tsx +++ b/src/components/reader/Page.tsx @@ -30,6 +30,8 @@ export function imageStyle(settings: IReaderSettings): CSSProperties { objectFit: 'contain', }; + const doublePageStyling: CSSProperties = {}; + const continuesVerticalStyling: CSSProperties = { marginBottom: '15px', }; @@ -53,6 +55,7 @@ export function imageStyle(settings: IReaderSettings): CSSProperties { return { ...baseStyling, + ...(isDoublePageReader ? doublePageStyling : undefined), ...(isHorizontal ? continuesHorizontalStyling : undefined), ...(isVertical ? continuesVerticalStyling : undefined), ...(settings.fitPageToWindow && !isHorizontal ? fitToPageStyling : undefined), @@ -74,7 +77,6 @@ export const Page = forwardRef((props: IProps, ref: any) => { const isMobileWidth = useMediaQuery(theme.breakpoints.down('md')); const imgStyle = imageStyle(settings); - const isDoublePageReader = ['DoubleRTL', 'DoubleLTR'].includes(settings.readerType); return ( { flexDirection: 'column', alignItems: 'center', justifyContent: 'center', - minWidth: isDoublePageReader ? '100%' : undefined, }} > { @@ -18,69 +17,53 @@ const isSpreadPage = (image: HTMLImageElement): boolean => { return aspectRatio < 1; }; -const isSinglePage = (index: number, spreadPages: boolean[], offsetFirstPage: boolean): boolean => { - // Page is single if it is spread page - if (spreadPages[index] || spreadPages[index + 1]) return true; - // Page is single if it is last page - if (index === spreadPages.length - 1) return true; - // Page is single if number of single pages since last spread is odd - const previousSpreadIndex = spreadPages.lastIndexOf(true, index - 1); - const numberOfNonSpreads = index - (previousSpreadIndex + 1); - return offsetFirstPage ? numberOfNonSpreads % 2 === 0 : numberOfNonSpreads % 2 === 1; -}; - export function DoublePagedPager(props: IReaderProps) { const { pages, settings, setCurPage, initialPage, curPage, chapter, nextChapter, prevChapter } = props; const selfRef = useRef(null); + const pagesToDisplayStateRef = useRef([]); + const pageToPrevSpreadPageRef = useRef([]); + const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false)); const [pagesLoadState, setPagesLoadState] = useState(Array(pages.length).fill(false)); - function getPagesToDisplay(): number { - let pagesToDisplay = 1; // has to be at least one so skipping forward while pages are still loading is possible - if (curPage < pages.length) { - if (pagesLoadState[curPage]) { - pagesToDisplay = 1; - if (pagesToSpreadState[curPage]) return pagesToDisplay; - } - } - if (curPage + 1 < pages.length) { - if (pagesLoadState[curPage + 1]) { - if (isSinglePage(curPage, pagesToSpreadState, settings.offsetFirstPage)) return pagesToDisplay; - pagesToDisplay = 2; - } - } - - return pagesToDisplay; - } - - function pagesToGoBack() { - // If previous page is single page, go only one page pack - if (isSinglePage(curPage - 2, pagesToSpreadState, settings.offsetFirstPage)) { - return 1; - } - - // Otherwise go two pages back - return 2; - } - function nextPage() { - if (curPage < pages.length - 1) { - const nextCurPage = curPage + getPagesToDisplay(); - setCurPage(nextCurPage >= pages.length ? pages.length - 1 : nextCurPage); - } else if (settings.loadNextOnEnding) { + const setNextPage = (page: number) => setCurPage(page === -1 ? pages.length - 1 : page); + + const isLastPageDisplayed = pagesToDisplayStateRef.current[pages.length - 1]; + if (isLastPageDisplayed && settings.loadNextOnEnding) { + // make sure to set last page as current page so that the chapter gets marked as read before opening the next chapter + const isLastPage = curPage === pages.length - 1; + if (!isLastPage) { + setNextPage(pages.length - 1); + } + nextChapter(); + return; } + + const page = pagesToDisplayStateRef.current.findIndex((displayed, index) => !displayed && index > curPage); + setNextPage(page); } function prevPage() { - if (curPage > 0) { - const nextCurPage = curPage - pagesToGoBack(); - setCurPage(nextCurPage < 0 ? 0 : nextCurPage); - } else { + const setPrevPage = (page: number) => setCurPage(Math.max(page, 0)); + + const isFirstPageDisplayed = pagesToDisplayStateRef.current[0]; + if (isFirstPageDisplayed) { + // not important, but for consistency make sure that first page gets set as current page in case it was displayed + const isFirstPage = !curPage; + if (!isFirstPage) { + setPrevPage(0); + } + prevChapter(); + return; } + + const page = [...pagesToDisplayStateRef.current].slice(0, curPage).findLastIndex((displayed) => !displayed); + setPrevPage(page); } function goLeft() { @@ -136,16 +119,6 @@ export function DoublePagedPager(props: IReaderProps) { setCurPage(initialPage); }, [initialPage]); - useEffect(() => { - if (settings.offsetFirstPage) { - if (getPagesToDisplay() === 2) { - setCurPage(curPage + 1); - } - } else if (curPage > 0 && !isSinglePage(curPage - 1, pagesToSpreadState, settings.offsetFirstPage)) { - setCurPage(curPage - 1); - } - }, [settings.offsetFirstPage]); - useEffect(() => { const imageRequests: [number, ReturnType<(typeof requestManager)['requestImage']>][] = pages.map((page) => [ page.index, @@ -187,23 +160,61 @@ export function DoublePagedPager(props: IReaderProps) { height: 'auto', }} > - {getPagesToDisplay() === 2 ? ( - - ) : ( - {}} - settings={settings} - /> - )} + {pages.map(({ index, src }) => { + const prevSpreadPage = (() => { + let currentIndexOfPrevSpreadPage = pageToPrevSpreadPageRef.current[curPage]; + + if (currentIndexOfPrevSpreadPage === undefined) { + currentIndexOfPrevSpreadPage = Math.max(pagesToSpreadState.lastIndexOf(true, curPage), 0); + + const areAllPrevPagesLoaded = pagesLoadState.slice(0, curPage).every(Boolean); + if (areAllPrevPagesLoaded) { + pageToPrevSpreadPageRef.current[curPage] = currentIndexOfPrevSpreadPage; + } + } + + return currentIndexOfPrevSpreadPage > 0 + ? currentIndexOfPrevSpreadPage + 1 + : currentIndexOfPrevSpreadPage; + })(); + + // index of first page after a spread page will be 0 + const normalizedCurPageIndexForSpreadPages = curPage - prevSpreadPage; + + const isFirstPage = curPage === 0; + // only offset pages before the first spread page, after the first spread page handle as if "offsetFirstPage" is disabled + const firstPageOffset = + Number(settings.offsetFirstPage) + + Number(settings.offsetFirstPage && !normalizedCurPageIndexForSpreadPages && !isFirstPage); + + const normalizedCurPageIndex = normalizedCurPageIndexForSpreadPages - firstPageOffset; + + const isCurPageEven = !(normalizedCurPageIndex % 2); + const secondPage = curPage + (isCurPageEven ? 1 : -1); + + const isCurPage = index === curPage; + const isSecondPage = index === secondPage; + + const isCurrentPageSpreadPage = pagesToSpreadState[curPage]; + const isSecondPageSpreadPage = pagesToSpreadState[secondPage]; + const hasSpreadPage = isCurrentPageSpreadPage || isSecondPageSpreadPage; + + const displaySecondPage = isSecondPage && !hasSpreadPage; + const displayPage = isCurPage || displaySecondPage; + + pagesToDisplayStateRef.current[index] = displayPage; + + return ( + {}} + settings={settings} + display={displayPage} + /> + ); + })} );