diff --git a/src/components/landing/QueryLanding.tsx b/src/components/landing/QueryLanding.tsx index 8b31a9759..2718a5e2f 100644 --- a/src/components/landing/QueryLanding.tsx +++ b/src/components/landing/QueryLanding.tsx @@ -15,6 +15,7 @@ import { } from '@phosphor-icons/react' import { useToast } from '~/components/ToastProvider' +import { useInView } from '~/hooks/useInView' import { usePrefersReducedMotion } from '~/utils/usePrefersReducedMotion' import { LibraryLanding, type LibraryLandingConfig } from './LibraryLanding' @@ -217,6 +218,8 @@ function QueryCachePanel() { const prefersReducedMotion = usePrefersReducedMotion() const queryClient = useQueryClient() const { notify } = useToast() + const rootRef = React.useRef(null) + const inView = useInView(rootRef) const serverRowsRef = React.useRef>( Object.fromEntries(queryHeroRows.map((row) => [row.id, { ...row.seed }])), ) @@ -304,7 +307,7 @@ function QueryCachePanel() { const rows = queryHeroRows.map((row, index) => { const query = rowQueries[index] - const elapsed = now - query.dataUpdatedAt + const elapsed = Math.max(0, now - query.dataUpdatedAt) const drained = Math.max( 0, Math.min(100, (1 - elapsed / row.staleTime) * 100), @@ -359,22 +362,29 @@ function QueryCachePanel() { // Per-frame rather than per-second: the gauges interpolate here instead of // through a CSS transition, so their drain rate tracks elapsed time exactly. React.useEffect(() => { + if (!inView) return + if (prefersReducedMotion === true) { + setNow(Date.now()) const id = setInterval(() => setNow(Date.now()), 1000) return () => clearInterval(id) } + let frame = 0 const tick = () => { setNow(Date.now()) frame = requestAnimationFrame(tick) } - let frame = requestAnimationFrame(tick) + tick() return () => cancelAnimationFrame(frame) - }, [prefersReducedMotion]) + }, [inView, prefersReducedMotion]) return ( -
+