From edc3fc951e4d98a7736b3376ba3d5bcc73607baf Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Wed, 18 Feb 2026 13:10:24 -0600 Subject: [PATCH] Optimize now-line updates and scrolling performance in TVChannelGuide --- frontend/src/pages/Guide.jsx | 46 +++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/Guide.jsx b/frontend/src/pages/Guide.jsx index 8ef73f04..2c730edd 100644 --- a/frontend/src/pages/Guide.jsx +++ b/frontend/src/pages/Guide.jsx @@ -125,12 +125,14 @@ export default function TVChannelGuide({ startDate, endDate }) { const tvGuideRef = useRef(null); // Ref for the main tv-guide wrapper const isSyncingScroll = useRef(false); const guideScrollLeftRef = useRef(0); + const nowLineRef = useRef(null); + const [settledScrollLeft, setSettledScrollLeft] = useState(0); + const scrollDebounceRef = useRef(null); const { ref: guideContainerRef, width: guideWidth, height: guideHeight, } = useElementSize(); - const [guideScrollLeft, setGuideScrollLeft] = useState(0); // Decide if 'All Channel Groups' should be enabled (based on total channel count) useEffect(() => { @@ -348,14 +350,14 @@ export default function TVChannelGuide({ startDate, endDate }) { isSyncingScroll.current = true; timelineRef.current.scrollLeft = scrollLeft; guideScrollLeftRef.current = scrollLeft; - setGuideScrollLeft(scrollLeft); + updateNowLine(); requestAnimationFrame(() => { isSyncingScroll.current = false; }); } else if (scrollLeft !== guideScrollLeftRef.current) { // Update ref even if timeline was already synced guideScrollLeftRef.current = scrollLeft; - setGuideScrollLeft(scrollLeft); + updateNowLine(); } }; @@ -366,11 +368,11 @@ export default function TVChannelGuide({ startDate, endDate }) { }; }, []); - // Update "now" every second + // Update "now" every 60 seconds (on a 24h guide, per-second is imperceptible) useEffect(() => { const interval = setInterval(() => { setNow(getNow()); - }, 1000); + }, 60000); return () => clearInterval(interval); }, []); @@ -380,6 +382,25 @@ export default function TVChannelGuide({ startDate, endDate }) { [now, start, end] ); + // Update the now-line DOM element directly (no React re-render) + const updateNowLine = useCallback(() => { + if (nowLineRef.current) { + nowLineRef.current.style.left = `${nowPosition + CHANNEL_WIDTH - guideScrollLeftRef.current}px`; + } + // Debounce horizontal culling update — fires 150ms after scrolling stops + if (scrollDebounceRef.current) { + clearTimeout(scrollDebounceRef.current); + } + scrollDebounceRef.current = setTimeout(() => { + setSettledScrollLeft(guideScrollLeftRef.current); + }, 150); + }, [nowPosition]); + + // Sync now-line whenever nowPosition changes (every 60s) + useEffect(() => { + updateNowLine(); + }, [updateNowLine]); + useEffect(() => { const tvGuide = tvGuideRef.current; @@ -418,7 +439,7 @@ export default function TVChannelGuide({ startDate, endDate }) { // Update the ref to keep state in sync guideScrollLeftRef.current = newScrollLeft; - setGuideScrollLeft(newScrollLeft); + updateNowLine(); } }; @@ -448,7 +469,7 @@ export default function TVChannelGuide({ startDate, endDate }) { if (guide && timeline && guide.scrollLeft !== timeline.scrollLeft) { timeline.scrollLeft = guide.scrollLeft; guideScrollLeftRef.current = guide.scrollLeft; - setGuideScrollLeft(guide.scrollLeft); + updateNowLine(); } lastCheck = timestamp; } @@ -485,7 +506,7 @@ export default function TVChannelGuide({ startDate, endDate }) { if (currentScroll !== lastScrollLeft) { timeline.scrollLeft = currentScroll; guideScrollLeftRef.current = currentScroll; - setGuideScrollLeft(currentScroll); + updateNowLine(); lastScrollLeft = currentScroll; stableFrames = 0; return true; // Still scrolling @@ -581,7 +602,7 @@ export default function TVChannelGuide({ startDate, endDate }) { } guideScrollLeftRef.current = nextLeft; - setGuideScrollLeft(nextLeft); + updateNowLine(); requestAnimationFrame(() => { isSyncingScroll.current = false; @@ -743,7 +764,7 @@ export default function TVChannelGuide({ startDate, endDate }) { } guideScrollLeftRef.current = nextLeft; - setGuideScrollLeft(nextLeft); + updateNowLine(); isSyncingScroll.current = true; if (guideRef.current) { @@ -1026,6 +1047,7 @@ export default function TVChannelGuide({ startDate, endDate }) { guideWidth || (typeof window !== 'undefined' ? window.innerWidth : 1200), timelineStartMs, + settledScrollLeft, // triggers row re-renders after scrolling stops }), [ filteredChannels, @@ -1038,6 +1060,7 @@ export default function TVChannelGuide({ startDate, endDate }) { contentWidth, guideWidth, timelineStartMs, + settledScrollLeft, ] ); @@ -1294,13 +1317,14 @@ export default function TVChannelGuide({ startDate, endDate }) { /> {nowPosition >= 0 && (