Center current chapter in drawer on open

Chapter drawer now opens with the active row pre-scrolled to the center
of the list instead of always starting at chapter #1. useLayoutEffect
measures via getBoundingClientRect so the scroll lands before paint —
no visible jump.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yiekheng 2026-04-12 13:04:35 +08:00
parent 43a2a6d3f8
commit cad59143a3

View File

@ -86,6 +86,8 @@ export function PageReader({
const pageElRef = useRef<Map<string, HTMLDivElement>>(new Map()); const pageElRef = useRef<Map<string, HTMLDivElement>>(new Map());
const observerRef = useRef<IntersectionObserver | null>(null); const observerRef = useRef<IntersectionObserver | null>(null);
const hiddenByScrollRef = useRef(false); const hiddenByScrollRef = useRef(false);
const drawerScrollRef = useRef<HTMLDivElement | null>(null);
const drawerActiveRef = useRef<HTMLAnchorElement | null>(null);
// Pages currently inside the observer's viewport margin. The scroll tick // Pages currently inside the observer's viewport margin. The scroll tick
// walks this small set instead of every loaded page. // walks this small set instead of every loaded page.
const intersectingPagesRef = useRef<Map<string, IntersectingPage>>(new Map()); const intersectingPagesRef = useRef<Map<string, IntersectingPage>>(new Map());
@ -322,6 +324,21 @@ export function PageReader({
[] []
); );
useLayoutEffect(() => {
if (!showDrawer) return;
const scroll = drawerScrollRef.current;
const active = drawerActiveRef.current;
if (!scroll || !active) return;
const scrollRect = scroll.getBoundingClientRect();
const activeRect = active.getBoundingClientRect();
const delta =
activeRect.top -
scrollRect.top -
scroll.clientHeight / 2 +
active.clientHeight / 2;
scroll.scrollTop = Math.max(0, scroll.scrollTop + delta);
}, [showDrawer]);
const currentChapter = const currentChapter =
chapters.find((c) => c.number === currentChapterNum) ?? chapters.find((c) => c.number === currentChapterNum) ??
chapters.find((c) => c.number === startChapterNumber); chapters.find((c) => c.number === startChapterNumber);
@ -474,12 +491,16 @@ export function PageReader({
</span> </span>
</div> </div>
</div> </div>
<div className="overflow-y-auto max-h-[calc(75vh-4rem)] pb-safe"> <div
ref={drawerScrollRef}
className="overflow-y-auto max-h-[calc(75vh-4rem)] pb-safe"
>
{chapters.map((ch) => { {chapters.map((ch) => {
const isActive = ch.number === currentChapterNum; const isActive = ch.number === currentChapterNum;
return ( return (
<Link <Link
key={ch.number} key={ch.number}
ref={isActive ? drawerActiveRef : undefined}
href={`/manga/${mangaSlug}/${ch.number}`} href={`/manga/${mangaSlug}/${ch.number}`}
scroll={false} scroll={false}
className={`flex items-center gap-3 px-5 py-3 text-sm transition-colors ${ className={`flex items-center gap-3 px-5 py-3 text-sm transition-colors ${