From e5dacc0a7706cd352943747b32cd69fb5a510df4 Mon Sep 17 00:00:00 2001 From: XingfenD Date: Tue, 8 Sep 2026 15:28:27 +0800 Subject: [PATCH] =?UTF-8?q?fix(cbz):=20clamp=20locked=20cur=20at=20chapter?= =?UTF-8?q?=20end,=20hide=20=E8=BF=9E=E8=AF=BB=20on=20flat=20packs,=20sile?= =?UTF-8?q?nt=20restore,=20tighten=20prefetch=20storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/readers/CbzReader.tsx | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/frontend/src/readers/CbzReader.tsx b/frontend/src/readers/CbzReader.tsx index 4f93455..78d318b 100644 --- a/frontend/src/readers/CbzReader.tsx +++ b/frontend/src/readers/CbzReader.tsx @@ -22,7 +22,7 @@ function useStoredBool(key: string, def: boolean) { function useStoredPrefetch() { const [n, setN] = useState(() => { const s = localStorage.getItem("cbz-prefetch"); - if (s === null) return 2; // 键缺失 ≠ 0:新用户默认 2 + if (s === null || s === "") return 2; // 键缺失/空串 ≠ 0:新用户默认 2 const v = Number(s); return Number.isInteger(v) && v >= 0 && v <= 3 ? v : 2; }); @@ -130,7 +130,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome if (restored.current || !count || !boxRef.current) return; restored.current = true; const p = Number(initialLocator?.page); - if (Number.isInteger(p) && p > 0 && p < count) goPage(p, false); + if (Number.isInteger(p) && p > 0 && p < count) goPage(p, false, false); }, [count, initialLocator, ph]); // 跨章跳页:新窗口几何渲染后才能真正滚动(旧窗口 contentH 会把 scrollTop 夹断),故挂 pending 等 [ci] 生效 @@ -154,6 +154,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome setTop(t); if (count) { curRef.current = ph.pageAt(t + anchor + vh / 2); + if (locked) curRef.current = Math.min(curRef.current, w.end - 1); // 末屏中心落在章末卡片/他章 → 钉在本章末页 if (!locked) setCi(chapterIndexAt(chapters, curRef.current)); // 连读/扁平:当前章随滚动走(目录计数高亮) saver.report({ page: curRef.current }, (curRef.current + 1) / count); } @@ -185,7 +186,8 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome }); } - const cur = ph.pageAt(top + anchor + vh / 2); + const curRaw = ph.pageAt(top + anchor + vh / 2); + const cur = locked ? Math.min(curRaw, w.end - 1) : curRaw; // 锁章:视口落在章末卡片上时读数不越章(HUD/滑条/进度) useEffect(() => { if (toc) activeRow.current?.scrollIntoView({ block: "center" }); @@ -200,10 +202,10 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome }); } - // 统一定位入口:锁章下目标页在窗外 = 显式切章(滑条/书签/目录/章末卡片/恢复进度皆此一路径) - function goPage(i: number, smooth = true) { + // 统一定位入口:锁章下目标页在窗外 = 显式切章(滑条/书签/目录/章末卡片皆此一路径);恢复进度走 show=false 静默落位 + function goPage(i: number, smooth = true, show = true) { const t = Math.max(0, Math.min(count - 1, i)); - chrome.show(); + if (show) chrome.show(); if (locked && (t < w.start || t >= w.end)) { setCi(chapterIndexAt(chapters, t)); pending.current = t; @@ -253,9 +255,10 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome if (el && d !== 0 && topOfI < topY) applyShift(d); // 视口上方页高变化 → 补偿滚动 // 让未量高页的估高收敛到实测页高:同开本漫画量一页后 d≈0,拖拽滚动条不再逐帧打架 if (width > 0 && Math.abs(h - oldEst) / oldEst > 0.03) { + const a0 = ph.offset(w.start); // 基线取当前实际 anchor:连续 measure 交错时闭包 anchor 可能过期,防前缀份额双扣 const n = ph.setEst(h, topY); - // n×Δest 是全局位移;锁章下 anchor 同步变了(前缀窗外页),那部分不影响窗口相对几何,须扣减 - const corr = n * (h - oldEst) - (ph.offset(w.start) - anchor); + // n×Δest 是全局位移;锁章下窗外前缀页的那部分不影响窗口相对几何,须扣减 + const corr = n * (h - oldEst) - (ph.offset(w.start) - a0); if (el && corr) applyShift(corr); ratio.current = h / width; } @@ -337,13 +340,15 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
- + {chapters && chapters.length >= 2 && ( + + )} {locked && (