diff --git a/apps/web/src/reader/CbzReader.tsx b/apps/web/src/reader/CbzReader.tsx index dce53c0..d75c56c 100644 --- a/apps/web/src/reader/CbzReader.tsx +++ b/apps/web/src/reader/CbzReader.tsx @@ -31,6 +31,7 @@ export function CbzReader({ const pendingVerticalAnchorRef = useRef(false); const verticalTrackingReadyRef = useRef(false); const verticalUserScrollRef = useRef(false); + const verticalAnchorTargetPageRef = useRef(null); const verticalAnchorFrameRef = useRef(null); const verticalAnchorAttemptRef = useRef(0); const verticalAnchorStableFramesRef = useRef(0); @@ -82,6 +83,23 @@ export function CbzReader({ if (verticalAnchorFrameRef.current !== null) cancelAnimationFrame(verticalAnchorFrameRef.current); verticalAnchorFrameRef.current = null; }, []); + const commitVisiblePage = useCallback( + (stage: HTMLElement, allowInitialCommit = false) => { + if (!pages || !verticalTrackingReadyRef.current) return; + if (!allowInitialCommit && !verticalUserScrollRef.current) return; + const stageRect = stage.getBoundingClientRect(); + const visiblePage = majorityVisiblePage( + Array.from(stage.querySelectorAll("[data-reader-page]")).map((element) => { + const rect = element.getBoundingClientRect(); + return { page: Number(element.dataset.readerPage), top: rect.top, bottom: rect.bottom }; + }), + stageRect.top, + stageRect.bottom + ); + if (visiblePage && visiblePage !== currentPage) onPageCommit(clampReaderPage(visiblePage, pages.pageCount), pages.pageCount, 1, "queued"); + }, + [currentPage, onPageCommit, pages] + ); const stabilizeVerticalAnchor = useCallback( (targetPage: number) => { clearVerticalAnchorFrame(); @@ -106,6 +124,7 @@ export function CbzReader({ if (verticalAnchorStableFramesRef.current >= VERTICAL_ANCHOR_STABLE_FRAMES) { verticalTrackingReadyRef.current = true; + commitVisiblePage(stage, true); return; } verticalAnchorAttemptRef.current += 1; @@ -116,7 +135,7 @@ export function CbzReader({ verticalAnchorFrameRef.current = requestAnimationFrame(measure); }, - [clearVerticalAnchorFrame] + [clearVerticalAnchorFrame, commitVisiblePage] ); const go = useCallback( @@ -178,10 +197,11 @@ export function CbzReader({ pendingVerticalAnchorRef.current = false; verticalTrackingReadyRef.current = false; verticalUserScrollRef.current = false; + verticalAnchorTargetPageRef.current = null; clearVerticalAnchorFrame(); return; } - if (previousModeRef.current !== "vertical") { + if (previousModeRef.current !== "vertical" || (!verticalUserScrollRef.current && verticalAnchorTargetPageRef.current !== currentPage)) { pendingVerticalAnchorRef.current = true; verticalTrackingReadyRef.current = false; verticalUserScrollRef.current = false; @@ -194,6 +214,7 @@ export function CbzReader({ return; } pendingVerticalAnchorRef.current = false; + verticalAnchorTargetPageRef.current = currentPage; stabilizeVerticalAnchor(currentPage); } previousModeRef.current = mode; @@ -213,17 +234,7 @@ export function CbzReader({ const keyTarget = typeof window === "undefined" ? null : window; const updateVisiblePage = () => { frameId = 0; - if (!verticalTrackingReadyRef.current || !verticalUserScrollRef.current) return; - const stageRect = stage.getBoundingClientRect(); - const visiblePage = majorityVisiblePage( - Array.from(stage.querySelectorAll("[data-reader-page]")).map((element) => { - const rect = element.getBoundingClientRect(); - return { page: Number(element.dataset.readerPage), top: rect.top, bottom: rect.bottom }; - }), - stageRect.top, - stageRect.bottom - ); - if (visiblePage && visiblePage !== currentPage) onPageCommit(clampReaderPage(visiblePage, pages.pageCount), pages.pageCount, 1, "queued"); + commitVisiblePage(stage); }; const onScroll = () => { if (frameId) return; @@ -243,7 +254,7 @@ export function CbzReader({ keyTarget?.removeEventListener("keydown", markUserScrollKey); stage.removeEventListener("scroll", onScroll); }; - }, [currentPage, mode, onPageCommit, pages]); + }, [commitVisiblePage, mode, pages]); useEffect(() => () => clearVerticalAnchorFrame(), [clearVerticalAnchorFrame]); diff --git a/apps/web/src/reader/PdfReader.tsx b/apps/web/src/reader/PdfReader.tsx index 0031209..1afcc9f 100644 --- a/apps/web/src/reader/PdfReader.tsx +++ b/apps/web/src/reader/PdfReader.tsx @@ -157,6 +157,7 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon const pendingVerticalAnchorRef = useRef(false); const verticalTrackingReadyRef = useRef(false); const verticalUserScrollRef = useRef(false); + const verticalAnchorTargetPageRef = useRef(null); const verticalAnchorFrameRef = useRef(null); const verticalAnchorAttemptRef = useRef(0); const verticalAnchorStableFramesRef = useRef(0); @@ -178,6 +179,23 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon if (verticalAnchorFrameRef.current !== null) cancelAnimationFrame(verticalAnchorFrameRef.current); verticalAnchorFrameRef.current = null; }, []); + const commitVisiblePage = useCallback( + (stage: HTMLElement, allowInitialCommit = false) => { + if (!verticalTrackingReadyRef.current) return; + if (!allowInitialCommit && !verticalUserScrollRef.current) return; + const stageRect = stage.getBoundingClientRect(); + const visiblePage = majorityVisiblePage( + Array.from(stage.querySelectorAll("[data-reader-page]")).map((element) => { + const rect = element.getBoundingClientRect(); + return { page: Number(element.dataset.readerPage), top: rect.top, bottom: rect.bottom }; + }), + stageRect.top, + stageRect.bottom + ); + if (visiblePage && visiblePage !== currentPage) onPageCommit(clampReaderPage(visiblePage, pages), pages, 1, "queued"); + }, + [currentPage, onPageCommit, pages] + ); const stabilizeVerticalAnchor = useCallback( (targetPage: number) => { clearVerticalAnchorFrame(); @@ -202,6 +220,7 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon if (verticalAnchorStableFramesRef.current >= VERTICAL_ANCHOR_STABLE_FRAMES) { verticalTrackingReadyRef.current = true; + commitVisiblePage(stage, true); return; } verticalAnchorAttemptRef.current += 1; @@ -212,7 +231,7 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon verticalAnchorFrameRef.current = requestAnimationFrame(measure); }, - [clearVerticalAnchorFrame] + [clearVerticalAnchorFrame, commitVisiblePage] ); const go = useCallback( @@ -244,10 +263,11 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon pendingVerticalAnchorRef.current = false; verticalTrackingReadyRef.current = false; verticalUserScrollRef.current = false; + verticalAnchorTargetPageRef.current = null; clearVerticalAnchorFrame(); return; } - if (previousModeRef.current !== "vertical") { + if (previousModeRef.current !== "vertical" || (!verticalUserScrollRef.current && verticalAnchorTargetPageRef.current !== currentPage)) { pendingVerticalAnchorRef.current = true; verticalTrackingReadyRef.current = false; verticalUserScrollRef.current = false; @@ -260,6 +280,7 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon return; } pendingVerticalAnchorRef.current = false; + verticalAnchorTargetPageRef.current = currentPage; stabilizeVerticalAnchor(currentPage); } previousModeRef.current = mode; @@ -279,17 +300,7 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon const keyTarget = typeof window === "undefined" ? null : window; const updateVisiblePage = () => { frameId = 0; - if (!verticalTrackingReadyRef.current || !verticalUserScrollRef.current) return; - const stageRect = stage.getBoundingClientRect(); - const visiblePage = majorityVisiblePage( - Array.from(stage.querySelectorAll("[data-reader-page]")).map((element) => { - const rect = element.getBoundingClientRect(); - return { page: Number(element.dataset.readerPage), top: rect.top, bottom: rect.bottom }; - }), - stageRect.top, - stageRect.bottom - ); - if (visiblePage && visiblePage !== currentPage) onPageCommit(clampReaderPage(visiblePage, pages), pages, 1, "queued"); + commitVisiblePage(stage); }; const onScroll = () => { if (frameId) return; @@ -309,7 +320,7 @@ export function PdfReader({ url, page, backHref, zoom, mode, onPageCommit, onCon keyTarget?.removeEventListener("keydown", markUserScrollKey); stage.removeEventListener("scroll", onScroll); }; - }, [currentPage, mode, onPageCommit, pages]); + }, [commitVisiblePage, mode, pages]); useEffect(() => () => clearVerticalAnchorFrame(), [clearVerticalAnchorFrame]);