fix(cbz): attach resize observer via callback ref + drop vh from scroll-mode geometry deps — fixes constant per-page gaps and refetch storm on mobile

This commit is contained in:
2026-09-08 13:05:58 +08:00
parent 32b9d3a136
commit a43b73dcef
2 changed files with 14 additions and 7 deletions
+3
View File
@@ -38,6 +38,9 @@ The format loosely follows Keep a Changelog and can be adapted to the team's hab
### Fixed / 修复 ### Fixed / 修复
- Comic continuous-scroll mode is contiguous again on phones: the size observer used to miss the scroll container (it was attached while the reader still showed "loading", so the box didn't exist), leaving page geometry computed from a stale 480px default width while pages rendered at the real (e.g. 390px) width — every page gained a constant dead gap. The observer now attaches when the box actually mounts; additionally, mobile address-bar show/hide no longer rebuilds the scroll geometry in continuous/whole-page modes, which used to wipe measured page heights and re-trigger a burst of image requests.
- 手机端漫画「连读」模式页面不再上下脱节:尺寸监听器此前在阅读器还在「加载中」时就尝试挂载(滚动容器尚不存在),导致监听从未生效,页高按过时的 480px 默认宽计算而实际按手机真实宽度(如 390px)渲染,每页之间出现恒定空白断层;现改为容器真正挂载时再接管。另外,手机地址栏收展在连读/整页模式下不再重建滚动几何——此前会清空全部实测页高并引发一波图片重复请求。
- Dragging the comic scrollbar (or the page slider) is no longer gritty: scroll handling is coalesced to one update per frame instead of per event, prefetching waits until scrolling settles instead of firing mid-drag, height corrections are batched per frame, the virtual list re-estimates unseen pages from the first measured page size (scrollbar geometry stops morphing under your finger), and slider seeks land instantly instead of restarting a smooth-scroll animation per notch. - Dragging the comic scrollbar (or the page slider) is no longer gritty: scroll handling is coalesced to one update per frame instead of per event, prefetching waits until scrolling settles instead of firing mid-drag, height corrections are batched per frame, the virtual list re-estimates unseen pages from the first measured page size (scrollbar geometry stops morphing under your finger), and slider seeks land instantly instead of restarting a smooth-scroll animation per notch.
- 漫画拖动滚动条(及页码滑条)不再发涩:滚动改为每帧合并处理而非逐事件响应,预取等滚动停稳再发(不再拖拽途中抢带宽),量高补偿按帧合并,虚拟列表用首个实测页高重估所有未加载页(滚动条几何不再拖拽中变形),滑条寻址即时落位(不再每格重启一段平滑动画)。 - 漫画拖动滚动条(及页码滑条)不再发涩:滚动改为每帧合并处理而非逐事件响应,预取等滚动停稳再发(不再拖拽途中抢带宽),量高补偿按帧合并,虚拟列表用首个实测页高重估所有未加载页(滚动条几何不再拖拽中变形),滑条寻址即时落位(不再每格重启一段平滑动画)。
- .txt/.md files encoded in GBK/GB2312/GB18030 or UTF-16 (typical web-novel exports) no longer render as mojibake: the reader strictly tries UTF-8 first and falls back to GB18030, recognizing UTF-16 by BOM. - .txt/.md files encoded in GBK/GB2312/GB18030 or UTF-16 (typical web-novel exports) no longer render as mojibake: the reader strictly tries UTF-8 first and falls back to GB18030, recognizing UTF-16 by BOM.
+11 -7
View File
@@ -106,22 +106,26 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
const [mode, setMode] = useStoredMode(); const [mode, setMode] = useStoredMode();
const fitH = mode === "fit"; const fitH = mode === "fit";
// vh 只在适高模式参与几何;滚动模式里地址栏收展改 vh 不得重建(会丢全部实测页高→页间错位+重挂图请求风暴)
const ph = useMemo( const ph = useMemo(
() => new PageHeights(count, fitH ? vh : width * ratio.current), () => new PageHeights(count, fitH ? vh : width * ratio.current),
[count, width, vh, fitH], [count, width, fitH ? vh : 0],
); );
// 容器尺寸自适应(尺寸变化后重建 PageHeights,阅读位置以当前滚动值为准) // 容器尺寸自适应:回调 ref 在滚动盒真正挂载时才观测(等页数请求期间的早退渲染里没有这个节点)
useEffect(() => { const roRef = useRef<ResizeObserver | null>(null);
const el = boxRef.current; function attachBox(el: HTMLDivElement | null) {
boxRef.current = el;
roRef.current?.disconnect();
roRef.current = null;
if (!el) return; if (!el) return;
const ro = new ResizeObserver(() => { const ro = new ResizeObserver(() => {
setWidth(Math.min(el.clientWidth, MAX_W)); setWidth(Math.min(el.clientWidth, MAX_W));
setVh(el.clientHeight); setVh(el.clientHeight);
}); });
ro.observe(el); ro.observe(el);
return () => ro.disconnect(); roRef.current = ro;
}, []); }
// 首次拿到 count 后恢复进度页 // 首次拿到 count 后恢复进度页
useEffect(() => { useEffect(() => {
@@ -222,7 +226,7 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
}} }}
> >
<div <div
ref={boxRef} ref={attachBox}
onScroll={onScroll} onScroll={onScroll}
onClick={onZoneClick} onClick={onZoneClick}
className={"h-full overflow-y-auto" + (mode !== "scroll" ? " snap-y snap-proximity" : "")} className={"h-full overflow-y-auto" + (mode !== "scroll" ? " snap-y snap-proximity" : "")}