fix(cbz): window-relative scroll geometry locks head boundary; default prefetch 2 when key absent
This commit is contained in:
@@ -21,7 +21,9 @@ function useStoredBool(key: string, def: boolean) {
|
||||
|
||||
function useStoredPrefetch() {
|
||||
const [n, setN] = useState(() => {
|
||||
const v = Number(localStorage.getItem("cbz-prefetch"));
|
||||
const s = localStorage.getItem("cbz-prefetch");
|
||||
if (s === null) return 2; // 键缺失 ≠ 0:新用户默认 2
|
||||
const v = Number(s);
|
||||
return Number.isInteger(v) && v >= 0 && v <= 3 ? v : 2;
|
||||
});
|
||||
useEffect(() => localStorage.setItem("cbz-prefetch", String(n)), [n]);
|
||||
@@ -105,6 +107,8 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
[chapters, count, ci, continuous, prefetch],
|
||||
);
|
||||
const locked = !!chapters && chapters.length >= 2 && !continuous;
|
||||
// 锁章下窗外前缀页不渲染 → scrollTop/页 top 一律窗口相对;读 ph(全局前缀和)前先加回 anchor
|
||||
const anchor = locked ? ph.offset(w.start) : 0;
|
||||
|
||||
// 容器尺寸自适应:回调 ref 在滚动盒真正挂载时才观测(等页数请求期间的早退渲染里没有这个节点)
|
||||
const roRef = useRef<ResizeObserver | null>(null);
|
||||
@@ -136,7 +140,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
if (!el) return;
|
||||
const t = pending.current;
|
||||
pending.current = null;
|
||||
el.scrollTop = ph.offset(t);
|
||||
el.scrollTop = ph.offset(t) - anchor;
|
||||
}, [ci, ph]);
|
||||
|
||||
// 滚动事件按帧合并;预取等滚动停稳再做——拖拽途中逐事件拉图=带宽/解码风暴
|
||||
@@ -149,7 +153,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
const t = el.scrollTop;
|
||||
setTop(t);
|
||||
if (count) {
|
||||
curRef.current = ph.pageAt(t + vh / 2);
|
||||
curRef.current = ph.pageAt(t + anchor + vh / 2);
|
||||
if (!locked) setCi(chapterIndexAt(chapters, curRef.current)); // 连读/扁平:当前章随滚动走(目录计数高亮)
|
||||
saver.report({ page: curRef.current }, (curRef.current + 1) / count);
|
||||
}
|
||||
@@ -181,7 +185,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
});
|
||||
}
|
||||
|
||||
const cur = ph.pageAt(top + vh / 2);
|
||||
const cur = ph.pageAt(top + anchor + vh / 2);
|
||||
|
||||
useEffect(() => {
|
||||
if (toc) activeRow.current?.scrollIntoView({ block: "center" });
|
||||
@@ -190,7 +194,10 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
function scrollToPage(i: number, smooth: boolean) {
|
||||
const el = boxRef.current;
|
||||
if (!el) return;
|
||||
el.scrollTo({ top: ph.offset(Math.max(w.start, Math.min(w.end - 1, i))), behavior: smooth ? "smooth" : "auto" });
|
||||
el.scrollTo({
|
||||
top: ph.offset(Math.max(w.start, Math.min(w.end - 1, i))) - anchor,
|
||||
behavior: smooth ? "smooth" : "auto",
|
||||
});
|
||||
}
|
||||
|
||||
// 统一定位入口:锁章下目标页在窗外 = 显式切章(滑条/书签/目录/章末卡片/恢复进度皆此一路径)
|
||||
@@ -232,14 +239,14 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
);
|
||||
|
||||
const fmt: string = book.page_url_fmt;
|
||||
const [a0, b0] = ph.range(top, vh, 2);
|
||||
const [a0, b0] = ph.range(top + anchor, vh, 2); // 视口位置换回全局空间再查页(锁章下 top 是窗口相对的)
|
||||
const a = Math.max(a0, w.start);
|
||||
const b = Math.min(b0, w.end); // 可见页永不超过章末;窗外页在隐藏预读层
|
||||
const contentH = locked ? ph.offset(w.end) + vh : ph.total();
|
||||
const contentH = locked ? ph.offset(w.end) - anchor + vh : ph.total();
|
||||
|
||||
function measure(i: number, h: number) {
|
||||
const el = boxRef.current;
|
||||
const topY = el ? el.scrollTop : 0;
|
||||
const topY = el ? el.scrollTop + anchor : 0; // 与 ph.offset 同处全局空间比较
|
||||
const oldEst = ph.estHeight;
|
||||
const topOfI = ph.offset(i);
|
||||
const d = ph.set(i, h);
|
||||
@@ -247,7 +254,9 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
// 让未量高页的估高收敛到实测页高:同开本漫画量一页后 d≈0,拖拽滚动条不再逐帧打架
|
||||
if (width > 0 && Math.abs(h - oldEst) / oldEst > 0.03) {
|
||||
const n = ph.setEst(h, topY);
|
||||
if (el && n) applyShift(n * (h - oldEst));
|
||||
// n×Δest 是全局位移;锁章下 anchor 同步变了(前缀窗外页),那部分不影响窗口相对几何,须扣减
|
||||
const corr = n * (h - oldEst) - (ph.offset(w.start) - anchor);
|
||||
if (el && corr) applyShift(corr);
|
||||
ratio.current = h / width;
|
||||
}
|
||||
bump((x) => x + 1);
|
||||
@@ -270,7 +279,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
{Array.from({ length: Math.max(0, b - a) }, (_, k) => {
|
||||
const i = a + k;
|
||||
return (
|
||||
<div key={i} className="absolute left-0 w-full" style={{ top: ph.offset(i) }}>
|
||||
<div key={i} className="absolute left-0 w-full" style={{ top: ph.offset(i) - anchor }}>
|
||||
<PageImg url={formatPageUrl(fmt, i)} width={width} onLoaded={(h) => measure(i, h)} />
|
||||
</div>
|
||||
);
|
||||
@@ -278,7 +287,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
{locked && (
|
||||
<div
|
||||
className="absolute left-0 grid w-full place-items-center"
|
||||
style={{ top: ph.offset(w.end), height: vh }}
|
||||
style={{ top: ph.offset(w.end) - anchor, height: vh }}
|
||||
>
|
||||
{w.end >= count ? (
|
||||
<span className="text-sm text-stone-500">— 全书完 —</span>
|
||||
@@ -295,7 +304,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
|
||||
{Array.from({ length: w.mountEnd - w.end }, (_, k) => {
|
||||
const i = w.end + k;
|
||||
return (
|
||||
<div key={i} className="absolute left-0 w-full" style={{ top: ph.offset(i) }}>
|
||||
<div key={i} className="absolute left-0 w-full" style={{ top: ph.offset(i) - anchor }}>
|
||||
<PageImg url={formatPageUrl(fmt, i)} width={width} onLoaded={(h) => measure(i, h)} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user