diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 0a743eaad..495f964c9 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -24,6 +24,7 @@ import { useNpmDownloadCounter } from '~/hooks/useNpmDownloadCounter' import { homepageNpmStatsSummaryQuery, ossStatsQuery } from '~/queries/stats' import { useLibrariesOverlay } from '~/contexts/LibrariesOverlayContext' import { fetchRecentPosts } from '~/utils/blog.functions' +import { usePrefersReducedMotion } from '~/utils/usePrefersReducedMotion' import { seo } from '~/utils/seo' export const Route = createFileRoute('/')({ @@ -565,8 +566,24 @@ function FrameworkAdapterGraph({ }) { const [activeAdapterIndex, setActiveAdapterIndex] = React.useState(0) const [flowProgress, setFlowProgress] = React.useState(0) + const [isVisible, setIsVisible] = React.useState(false) + const rootRef = React.useRef(null) + const prefersReducedMotion = usePrefersReducedMotion() React.useEffect(() => { + const root = rootRef.current + if (!root) return + + const observer = new IntersectionObserver(([entry]) => { + setIsVisible(Boolean(entry?.isIntersecting)) + }) + observer.observe(root) + return () => observer.disconnect() + }, []) + + React.useEffect(() => { + if (!isVisible || prefersReducedMotion !== false) return + const intervalId = window.setInterval(() => { setActiveAdapterIndex( (currentIndex) => (currentIndex + 1) % frameworkAdapterNodes.length, @@ -574,12 +591,10 @@ function FrameworkAdapterGraph({ }, 1150) return () => window.clearInterval(intervalId) - }, []) + }, [isVisible, prefersReducedMotion]) React.useEffect(() => { - if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { - return - } + if (!isVisible || prefersReducedMotion !== false) return let frameId = 0 let lastUpdate = 0 @@ -597,13 +612,14 @@ function FrameworkAdapterGraph({ frameId = window.requestAnimationFrame(update) return () => window.cancelAnimationFrame(frameId) - }, []) + }, [isVisible, prefersReducedMotion]) return ( // The node positions below are hard-coded against a 320×128 grid // (adapterGraphWidth/Height), so the whole graph is scaled as a unit to // fill the 460×233 slot rather than re-deriving every coordinate.