diff --git a/web/src/views/events/MotionPreviewsPane.tsx b/web/src/views/events/MotionPreviewsPane.tsx index 331a9af33..fc080c956 100644 --- a/web/src/views/events/MotionPreviewsPane.tsx +++ b/web/src/views/events/MotionPreviewsPane.tsx @@ -624,6 +624,9 @@ export default function MotionPreviewsPane({ const [hasVisibilityData, setHasVisibilityData] = useState(false); const clipObserver = useRef(null); + const [mountedClips, setMountedClips] = useState>(new Set()); + const mountObserver = useRef(null); + const recordingTimeRange = useMemo(() => { if (!motionRanges.length) { return null; @@ -788,15 +791,56 @@ export default function MotionPreviewsPane({ }; }, [scrollContainer]); + useEffect(() => { + if (!scrollContainer) { + return; + } + + const nearClipIds = new Set(); + mountObserver.current = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + const clipId = (entry.target as HTMLElement).dataset.clipId; + + if (!clipId) { + return; + } + + if (entry.isIntersecting) { + nearClipIds.add(clipId); + } else { + nearClipIds.delete(clipId); + } + }); + + setMountedClips(new Set(nearClipIds)); + }, + { + root: scrollContainer, + rootMargin: "200% 0px", + threshold: 0, + }, + ); + + scrollContainer + .querySelectorAll("[data-clip-id]") + .forEach((node) => { + mountObserver.current?.observe(node); + }); + + return () => { + mountObserver.current?.disconnect(); + }; + }, [scrollContainer]); + const clipRef = useCallback((node: HTMLElement | null) => { - if (!clipObserver.current) { + if (!node) { return; } try { - if (node) { - clipObserver.current.observe(node); - } + clipObserver.current?.observe(node); + mountObserver.current?.observe(node); } catch { // no op } @@ -864,31 +908,38 @@ export default function MotionPreviewsPane({ ) : (
{clipData.map( - ({ range, preview, fallbackFrameTimes, motionHeatmap }, idx) => ( -
- -
- ), + ({ range, preview, fallbackFrameTimes, motionHeatmap }, idx) => { + const clipId = `${camera.name}-${range.start_time}-${range.end_time}-${idx}`; + const isMounted = mountedClips.has(clipId); + + return ( +
+ {isMounted ? ( + + ) : ( +
+ )} +
+ ); + }, )}
)}