From 5abe18d5b8c598c7b4247955a642ab5eeffc91d3 Mon Sep 17 00:00:00 2001 From: cucadmuh <49571317+cucadmuh@users.noreply.github.com> Date: Wed, 6 May 2026 10:45:13 +0300 Subject: [PATCH] Perf/UI performance (#473) * perf(ui): defer off-screen Artist grid paint + rAF overlay scrollbar Apply content-visibility to artist tiles in .album-grid-wrap (aligned with album cards). Coalesce OverlayScrollArea thumb updates to one requestAnimationFrame per scroll burst. * perf(css): content-visibility on horizontal artist rails Apply the same off-screen deferral as album cards to `.album-grid .artist-card` (ArtistRow, Favorites, etc.). --- src/components/OverlayScrollArea.tsx | 19 ++++++++++++++++++- src/styles/components.css | 11 +++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/OverlayScrollArea.tsx b/src/components/OverlayScrollArea.tsx index 0b546a5c..30ade1a5 100644 --- a/src/components/OverlayScrollArea.tsx +++ b/src/components/OverlayScrollArea.tsx @@ -60,6 +60,8 @@ export default function OverlayScrollArea({ const perfFlags = usePerfProbeFlags(); const wrapRef = useRef(null); const viewportRef = useRef(null); + /** Coalesce burst scroll events to one thumb update per animation frame. */ + const scrollRecomputeRafRef = useRef(0); const [meta, setMeta] = useState({ thumbH: 0, thumbT: 0, visible: false }); const recompute = useCallback(() => { @@ -71,6 +73,21 @@ export default function OverlayScrollArea({ setMeta(computeOverlayScrollbarThumbMeta(vp, trackH)); }, []); + const scheduleScrollRecompute = useCallback(() => { + if (scrollRecomputeRafRef.current !== 0) return; + scrollRecomputeRafRef.current = requestAnimationFrame(() => { + scrollRecomputeRafRef.current = 0; + recompute(); + }); + }, [recompute]); + + useEffect(() => () => { + if (scrollRecomputeRafRef.current !== 0) { + cancelAnimationFrame(scrollRecomputeRafRef.current); + scrollRecomputeRafRef.current = 0; + } + }, []); + const measureKey = JSON.stringify(measureDeps ?? []); useLayoutEffect(() => { @@ -138,7 +155,7 @@ export default function OverlayScrollArea({ id={viewportId} ref={setViewportNode} className={viewportClass} - onScroll={perfFlags.disableOverlayScrollbars ? undefined : recompute} + onScroll={perfFlags.disableOverlayScrollbars ? undefined : scheduleScrollRecompute} onWheel={viewportOnWheel} onTouchMove={viewportOnTouchMove} > diff --git a/src/styles/components.css b/src/styles/components.css index 2fd21a52..ac2981f7 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -401,6 +401,12 @@ contain-intrinsic-size: 0 220px; } +/* Same wrap as Albums grid; defer layout/paint for off-screen artist tiles (helps scroll on WebKit/Linux). */ +.album-grid-wrap > .artist-card { + content-visibility: auto; + contain-intrinsic-size: 0 220px; +} + @media (min-width: 1024px) { .album-grid-wrap { @@ -420,6 +426,11 @@ contain-intrinsic-size: 0 240px; } +.album-grid .artist-card { + content-visibility: auto; + contain-intrinsic-size: 0 240px; +} + .album-card-more { flex: 0 0 clamp(140px, 15vw, 180px); display: flex;