Prevent blinking on page switch in double mode

This commit is contained in:
schroda
2024-09-16 22:24:08 +02:00
parent fe3bbedde9
commit 83ce1caee6
2 changed files with 89 additions and 77 deletions

View File

@@ -30,6 +30,8 @@ export function imageStyle(settings: IReaderSettings): CSSProperties {
objectFit: 'contain', objectFit: 'contain',
}; };
const doublePageStyling: CSSProperties = {};
const continuesVerticalStyling: CSSProperties = { const continuesVerticalStyling: CSSProperties = {
marginBottom: '15px', marginBottom: '15px',
}; };
@@ -53,6 +55,7 @@ export function imageStyle(settings: IReaderSettings): CSSProperties {
return { return {
...baseStyling, ...baseStyling,
...(isDoublePageReader ? doublePageStyling : undefined),
...(isHorizontal ? continuesHorizontalStyling : undefined), ...(isHorizontal ? continuesHorizontalStyling : undefined),
...(isVertical ? continuesVerticalStyling : undefined), ...(isVertical ? continuesVerticalStyling : undefined),
...(settings.fitPageToWindow && !isHorizontal ? fitToPageStyling : 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 isMobileWidth = useMediaQuery(theme.breakpoints.down('md'));
const imgStyle = imageStyle(settings); const imgStyle = imageStyle(settings);
const isDoublePageReader = ['DoubleRTL', 'DoubleLTR'].includes(settings.readerType);
return ( return (
<Box <Box
@@ -84,7 +86,6 @@ export const Page = forwardRef((props: IProps, ref: any) => {
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
minWidth: isDoublePageReader ? '100%' : undefined,
}} }}
> >
<SpinnerImage <SpinnerImage

View File

@@ -10,7 +10,6 @@ import { MouseEvent, useEffect, useRef, useState } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { IReaderProps } from '@/typings'; import { IReaderProps } from '@/typings';
import { Page } from '@/components/reader/Page'; import { Page } from '@/components/reader/Page';
import { DoublePage } from '@/components/reader/DoublePage';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
const isSpreadPage = (image: HTMLImageElement): boolean => { const isSpreadPage = (image: HTMLImageElement): boolean => {
@@ -18,69 +17,53 @@ const isSpreadPage = (image: HTMLImageElement): boolean => {
return aspectRatio < 1; 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) { export function DoublePagedPager(props: IReaderProps) {
const { pages, settings, setCurPage, initialPage, curPage, chapter, nextChapter, prevChapter } = props; const { pages, settings, setCurPage, initialPage, curPage, chapter, nextChapter, prevChapter } = props;
const selfRef = useRef<HTMLDivElement>(null); const selfRef = useRef<HTMLDivElement>(null);
const pagesToDisplayStateRef = useRef<boolean[]>([]);
const pageToPrevSpreadPageRef = useRef<number[]>([]);
const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false)); const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false));
const [pagesLoadState, setPagesLoadState] = useState<boolean[]>(Array(pages.length).fill(false)); const [pagesLoadState, setPagesLoadState] = useState<boolean[]>(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() { function nextPage() {
if (curPage < pages.length - 1) { const setNextPage = (page: number) => setCurPage(page === -1 ? pages.length - 1 : page);
const nextCurPage = curPage + getPagesToDisplay();
setCurPage(nextCurPage >= pages.length ? pages.length - 1 : nextCurPage); const isLastPageDisplayed = pagesToDisplayStateRef.current[pages.length - 1];
} else if (settings.loadNextOnEnding) { 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(); nextChapter();
return;
} }
const page = pagesToDisplayStateRef.current.findIndex((displayed, index) => !displayed && index > curPage);
setNextPage(page);
} }
function prevPage() { function prevPage() {
if (curPage > 0) { const setPrevPage = (page: number) => setCurPage(Math.max(page, 0));
const nextCurPage = curPage - pagesToGoBack();
setCurPage(nextCurPage < 0 ? 0 : nextCurPage); const isFirstPageDisplayed = pagesToDisplayStateRef.current[0];
} else { 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(); prevChapter();
return;
} }
const page = [...pagesToDisplayStateRef.current].slice(0, curPage).findLastIndex((displayed) => !displayed);
setPrevPage(page);
} }
function goLeft() { function goLeft() {
@@ -136,16 +119,6 @@ export function DoublePagedPager(props: IReaderProps) {
setCurPage(initialPage); setCurPage(initialPage);
}, [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(() => { useEffect(() => {
const imageRequests: [number, ReturnType<(typeof requestManager)['requestImage']>][] = pages.map((page) => [ const imageRequests: [number, ReturnType<(typeof requestManager)['requestImage']>][] = pages.map((page) => [
page.index, page.index,
@@ -187,23 +160,61 @@ export function DoublePagedPager(props: IReaderProps) {
height: 'auto', height: 'auto',
}} }}
> >
{getPagesToDisplay() === 2 ? ( {pages.map(({ index, src }) => {
<DoublePage const prevSpreadPage = (() => {
key={curPage} let currentIndexOfPrevSpreadPage = pageToPrevSpreadPageRef.current[curPage];
index={curPage}
image1src={pages[curPage].src} if (currentIndexOfPrevSpreadPage === undefined) {
image2src={pages[curPage + 1].src} currentIndexOfPrevSpreadPage = Math.max(pagesToSpreadState.lastIndexOf(true, curPage), 0);
settings={settings}
/> const areAllPrevPagesLoaded = pagesLoadState.slice(0, curPage).every(Boolean);
) : ( if (areAllPrevPagesLoaded) {
<Page pageToPrevSpreadPageRef.current[curPage] = currentIndexOfPrevSpreadPage;
key={curPage} }
index={curPage} }
src={pages[curPage].src}
onImageLoad={() => {}} return currentIndexOfPrevSpreadPage > 0
settings={settings} ? 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 (
<Page
key={src}
index={index}
src={src}
onImageLoad={() => {}}
settings={settings}
display={displayPage}
/>
);
})}
</Box> </Box>
</Box> </Box>
); );