fix(web): lecteurs CBZ/CBR et PDF — synchroniser la page visible au chargement en mode vertical avec garde anti-boucle, alignée sur le scroll initial réel (5e correctif bug #39)
This commit is contained in:
@ -10,6 +10,7 @@ type PageCommitStrategy = "immediate" | "queued";
|
||||
const VERTICAL_ANCHOR_TOLERANCE_PX = 24;
|
||||
const VERTICAL_ANCHOR_STABLE_FRAMES = 18;
|
||||
const VERTICAL_ANCHOR_MAX_ATTEMPTS = 180;
|
||||
const VERTICAL_INITIAL_SYNC_MAX_ATTEMPTS = 180;
|
||||
|
||||
export function CbzReader({
|
||||
bookId,
|
||||
@ -33,6 +34,9 @@ export function CbzReader({
|
||||
const verticalUserScrollRef = useRef(false);
|
||||
const verticalAnchorTargetPageRef = useRef<number | null>(null);
|
||||
const verticalAnchorFrameRef = useRef<number | null>(null);
|
||||
const verticalInitialSyncFrameRef = useRef<number | null>(null);
|
||||
const verticalInitialSyncAttemptRef = useRef(0);
|
||||
const verticalInitialSyncDoneRef = useRef(false);
|
||||
const verticalAnchorAttemptRef = useRef(0);
|
||||
const verticalAnchorStableFramesRef = useRef(0);
|
||||
const [pages, setPages] = useState<CbzPagesDto | null>(null);
|
||||
@ -83,10 +87,15 @@ export function CbzReader({
|
||||
if (verticalAnchorFrameRef.current !== null) cancelAnimationFrame(verticalAnchorFrameRef.current);
|
||||
verticalAnchorFrameRef.current = null;
|
||||
}, []);
|
||||
const clearVerticalInitialSync = useCallback(() => {
|
||||
if (verticalInitialSyncFrameRef.current !== null) cancelAnimationFrame(verticalInitialSyncFrameRef.current);
|
||||
verticalInitialSyncFrameRef.current = null;
|
||||
}, []);
|
||||
const commitVisiblePage = useCallback(
|
||||
(stage: HTMLElement, allowInitialCommit = false) => {
|
||||
if (!pages || !verticalTrackingReadyRef.current) return;
|
||||
if (!allowInitialCommit && !verticalUserScrollRef.current) return;
|
||||
if (allowInitialCommit && verticalInitialSyncDoneRef.current) return;
|
||||
const stageRect = stage.getBoundingClientRect();
|
||||
const visiblePage = majorityVisiblePage(
|
||||
Array.from(stage.querySelectorAll<HTMLElement>("[data-reader-page]")).map((element) => {
|
||||
@ -96,10 +105,34 @@ export function CbzReader({
|
||||
stageRect.top,
|
||||
stageRect.bottom
|
||||
);
|
||||
if (visiblePage && visiblePage !== currentPage) onPageCommit(clampReaderPage(visiblePage, pages.pageCount), pages.pageCount, 1, "queued");
|
||||
if (visiblePage && visiblePage !== currentPage) {
|
||||
if (allowInitialCommit) verticalInitialSyncDoneRef.current = true;
|
||||
onPageCommit(clampReaderPage(visiblePage, pages.pageCount), pages.pageCount, 1, "queued");
|
||||
}
|
||||
},
|
||||
[currentPage, onPageCommit, pages]
|
||||
);
|
||||
const scheduleInitialVisibleSync = useCallback(
|
||||
(stage: HTMLElement) => {
|
||||
clearVerticalInitialSync();
|
||||
if (!(stage.scrollHeight > stage.clientHeight)) return;
|
||||
verticalInitialSyncAttemptRef.current = 0;
|
||||
|
||||
const sync = () => {
|
||||
verticalInitialSyncFrameRef.current = null;
|
||||
if (verticalUserScrollRef.current || verticalInitialSyncDoneRef.current) return;
|
||||
if (verticalTrackingReadyRef.current && stage.scrollTop > Math.max(32, stage.clientHeight * 0.5)) {
|
||||
commitVisiblePage(stage, true);
|
||||
}
|
||||
verticalInitialSyncAttemptRef.current += 1;
|
||||
if (verticalInitialSyncAttemptRef.current >= VERTICAL_INITIAL_SYNC_MAX_ATTEMPTS) return;
|
||||
verticalInitialSyncFrameRef.current = requestAnimationFrame(sync);
|
||||
};
|
||||
|
||||
verticalInitialSyncFrameRef.current = requestAnimationFrame(sync);
|
||||
},
|
||||
[clearVerticalInitialSync, commitVisiblePage]
|
||||
);
|
||||
const stabilizeVerticalAnchor = useCallback(
|
||||
(targetPage: number) => {
|
||||
clearVerticalAnchorFrame();
|
||||
@ -125,6 +158,7 @@ export function CbzReader({
|
||||
if (verticalAnchorStableFramesRef.current >= VERTICAL_ANCHOR_STABLE_FRAMES) {
|
||||
verticalTrackingReadyRef.current = true;
|
||||
commitVisiblePage(stage, true);
|
||||
scheduleInitialVisibleSync(stage);
|
||||
return;
|
||||
}
|
||||
verticalAnchorAttemptRef.current += 1;
|
||||
@ -135,7 +169,7 @@ export function CbzReader({
|
||||
|
||||
verticalAnchorFrameRef.current = requestAnimationFrame(measure);
|
||||
},
|
||||
[clearVerticalAnchorFrame, commitVisiblePage]
|
||||
[clearVerticalAnchorFrame, commitVisiblePage, scheduleInitialVisibleSync]
|
||||
);
|
||||
|
||||
const go = useCallback(
|
||||
@ -198,14 +232,19 @@ export function CbzReader({
|
||||
verticalTrackingReadyRef.current = false;
|
||||
verticalUserScrollRef.current = false;
|
||||
verticalAnchorTargetPageRef.current = null;
|
||||
verticalInitialSyncDoneRef.current = false;
|
||||
clearVerticalAnchorFrame();
|
||||
clearVerticalInitialSync();
|
||||
return;
|
||||
}
|
||||
if (previousModeRef.current !== "vertical" || (!verticalUserScrollRef.current && verticalAnchorTargetPageRef.current !== currentPage)) {
|
||||
const enteringVertical = previousModeRef.current !== "vertical";
|
||||
if (enteringVertical || (!verticalUserScrollRef.current && verticalAnchorTargetPageRef.current !== currentPage)) {
|
||||
pendingVerticalAnchorRef.current = true;
|
||||
verticalTrackingReadyRef.current = false;
|
||||
verticalUserScrollRef.current = false;
|
||||
if (enteringVertical) verticalInitialSyncDoneRef.current = false;
|
||||
clearVerticalAnchorFrame();
|
||||
clearVerticalInitialSync();
|
||||
}
|
||||
if (pendingVerticalAnchorRef.current) {
|
||||
const target = frameRef.current?.querySelector<HTMLElement>(`[data-reader-page="${currentPage}"]`);
|
||||
@ -218,7 +257,7 @@ export function CbzReader({
|
||||
stabilizeVerticalAnchor(currentPage);
|
||||
}
|
||||
previousModeRef.current = mode;
|
||||
}, [clearVerticalAnchorFrame, currentPage, mode, pages, stabilizeVerticalAnchor]);
|
||||
}, [clearVerticalAnchorFrame, clearVerticalInitialSync, currentPage, mode, pages, stabilizeVerticalAnchor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== "vertical" || !pages) return;
|
||||
@ -245,6 +284,7 @@ export function CbzReader({
|
||||
stage.addEventListener("pointerdown", markUserScroll, { passive: true });
|
||||
keyTarget?.addEventListener("keydown", markUserScrollKey);
|
||||
stage.addEventListener("scroll", onScroll, { passive: true });
|
||||
scheduleInitialVisibleSync(stage);
|
||||
updateVisiblePage();
|
||||
return () => {
|
||||
if (frameId) cancelAnimationFrame(frameId);
|
||||
@ -254,9 +294,10 @@ export function CbzReader({
|
||||
keyTarget?.removeEventListener("keydown", markUserScrollKey);
|
||||
stage.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [commitVisiblePage, mode, pages]);
|
||||
}, [commitVisiblePage, mode, pages, scheduleInitialVisibleSync]);
|
||||
|
||||
useEffect(() => () => clearVerticalAnchorFrame(), [clearVerticalAnchorFrame]);
|
||||
useEffect(() => () => clearVerticalInitialSync(), [clearVerticalInitialSync]);
|
||||
|
||||
if (documentError) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user