diff --git a/docker/frontend/src/ui/Filmstrip.tsx b/docker/frontend/src/ui/Filmstrip.tsx index 3ad262f..ed28327 100644 --- a/docker/frontend/src/ui/Filmstrip.tsx +++ b/docker/frontend/src/ui/Filmstrip.tsx @@ -92,12 +92,18 @@ export const Filmstrip = memo(function Filmstrip({ // and not `scroll-behavior: smooth` on the rail, which glides across the whole // roll: a scroll event a frame, and a fresh window asked for at each one, of // frames the reader never stops on. The rail is put where it is wanted. - useEffect(() => { + const centre = useCallback(() => { const box = rail.current; const el = box?.children[at] as HTMLElement | undefined; if (!box || !el) return; box.scrollLeft = el.offsetLeft - box.clientWidth / 2 + el.clientWidth / 2; - }, [at, frames]); + }, [at]); + + useEffect(() => { + centre(); + // The frames are in the dependency too: the frame in hand is a child of the + // rail, and until the folder arrives there is nothing to stand on. + }, [centre, frames]); // Scrolling and resizing both move what the rail can reach: the phone's rail // is a different window on the same roll, and the tiles it now needs are not @@ -115,14 +121,23 @@ export const Filmstrip = memo(function Filmstrip({ report(); }); }; + // A narrower rail is not the wide one with less of the roll in it: it is a + // different number of frames to the pixel the reader was standing on, so the + // same scrollLeft is a different frame — the strip left the frame on the + // stage behind and drew the roll from somewhere else down it. It is put back + // on that frame first, and the window is read from where it then stands. + const resized = () => { + centre(); + again(); + }; box.addEventListener('scroll', again, { passive: true }); - const ro = new ResizeObserver(again); + const ro = new ResizeObserver(resized); ro.observe(box); return () => { box.removeEventListener('scroll', again); ro.disconnect(); }; - }, [report]); + }, [report, centre]); return ( // A wheel has no sideways axis, so a plain tick over the roll walks it along.