diff --git a/app/javascript/components/shared/maplibre/MapLibre.tsx b/app/javascript/components/shared/maplibre/MapLibre.tsx index a7ddf4191..a6ae5f181 100644 --- a/app/javascript/components/shared/maplibre/MapLibre.tsx +++ b/app/javascript/components/shared/maplibre/MapLibre.tsx @@ -13,7 +13,7 @@ import type { Style } from 'maplibre-gl'; import invariant from 'tiny-invariant'; -import { useStyle } from './hooks'; +import { useStyle, useElementVisible } from './hooks'; import { StyleControl } from './StyleControl'; const Context = createContext<{ map?: Map | null }>({}); @@ -35,6 +35,7 @@ export function MapLibre({ children, layers }: MapLibreProps) { [] ); const containerRef = useRef(null); + const visible = useElementVisible(containerRef); const [map, setMap] = useState(); const onStyleChange = useCallback( @@ -48,7 +49,7 @@ export function MapLibre({ children, layers }: MapLibreProps) { const { style, ...mapStyleProps } = useStyle(layers, onStyleChange); useEffect(() => { - if (isSupported && !map) { + if (isSupported && visible && !map) { invariant(containerRef.current, 'Map container not found'); const map = new Map({ container: containerRef.current, @@ -59,7 +60,7 @@ export function MapLibre({ children, layers }: MapLibreProps) { setMap(map); }); } - }, [map, style, isSupported]); + }, [map, style, visible, isSupported]); if (!isSupported) { return ( diff --git a/app/javascript/components/shared/maplibre/hooks.ts b/app/javascript/components/shared/maplibre/hooks.ts index 56a59ddf3..078e5fb15 100644 --- a/app/javascript/components/shared/maplibre/hooks.ts +++ b/app/javascript/components/shared/maplibre/hooks.ts @@ -1,4 +1,10 @@ -import { useCallback, useEffect, useState, useMemo } from 'react'; +import { + useCallback, + useEffect, + useState, + useMemo, + type RefObject +} from 'react'; import type { LngLatBoundsLike, LngLat, @@ -118,3 +124,30 @@ export function useStyle( return { style, layers, setStyle, setLayerEnabled, setLayerOpacity }; } + +function isElementVisible( + element: HTMLElement, + callback: (visible: boolean) => void +) { + if (element.offsetWidth > 0 && element.offsetHeight > 0) { + callback(true); + } else { + callback(false); + const observer = new IntersectionObserver( + (entries) => callback(entries[0].isIntersecting == true), + { threshold: [0] } + ); + observer.observe(element); + return () => observer.unobserve(element); + } +} + +export function useElementVisible(element: RefObject) { + const [visible, setVisible] = useState(false); + useEffect(() => { + if (element.current) { + return isElementVisible(element.current, setVisible); + } + }, [element]); + return visible; +}