mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-17 13:48:21 +03:00
lazy mount motion preview clips to reduce DOM overhead
This commit is contained in:
parent
d1793207b4
commit
297bc5e8a0
@ -624,6 +624,9 @@ export default function MotionPreviewsPane({
|
|||||||
const [hasVisibilityData, setHasVisibilityData] = useState(false);
|
const [hasVisibilityData, setHasVisibilityData] = useState(false);
|
||||||
const clipObserver = useRef<IntersectionObserver | null>(null);
|
const clipObserver = useRef<IntersectionObserver | null>(null);
|
||||||
|
|
||||||
|
const [mountedClips, setMountedClips] = useState<Set<string>>(new Set());
|
||||||
|
const mountObserver = useRef<IntersectionObserver | null>(null);
|
||||||
|
|
||||||
const recordingTimeRange = useMemo(() => {
|
const recordingTimeRange = useMemo(() => {
|
||||||
if (!motionRanges.length) {
|
if (!motionRanges.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -788,15 +791,56 @@ export default function MotionPreviewsPane({
|
|||||||
};
|
};
|
||||||
}, [scrollContainer]);
|
}, [scrollContainer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!scrollContainer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nearClipIds = new Set<string>();
|
||||||
|
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<HTMLElement>("[data-clip-id]")
|
||||||
|
.forEach((node) => {
|
||||||
|
mountObserver.current?.observe(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mountObserver.current?.disconnect();
|
||||||
|
};
|
||||||
|
}, [scrollContainer]);
|
||||||
|
|
||||||
const clipRef = useCallback((node: HTMLElement | null) => {
|
const clipRef = useCallback((node: HTMLElement | null) => {
|
||||||
if (!clipObserver.current) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (node) {
|
clipObserver.current?.observe(node);
|
||||||
clipObserver.current.observe(node);
|
mountObserver.current?.observe(node);
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
@ -864,31 +908,38 @@ export default function MotionPreviewsPane({
|
|||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 gap-2 pb-2 sm:grid-cols-2 md:gap-4 xl:grid-cols-4">
|
<div className="grid grid-cols-1 gap-2 pb-2 sm:grid-cols-2 md:gap-4 xl:grid-cols-4">
|
||||||
{clipData.map(
|
{clipData.map(
|
||||||
({ range, preview, fallbackFrameTimes, motionHeatmap }, idx) => (
|
({ range, preview, fallbackFrameTimes, motionHeatmap }, idx) => {
|
||||||
<div
|
const clipId = `${camera.name}-${range.start_time}-${range.end_time}-${idx}`;
|
||||||
key={`${camera.name}-${range.start_time}-${range.end_time}-${preview?.src ?? "none"}-${idx}`}
|
const isMounted = mountedClips.has(clipId);
|
||||||
data-clip-id={`${camera.name}-${range.start_time}-${range.end_time}-${idx}`}
|
|
||||||
ref={clipRef}
|
return (
|
||||||
>
|
<div
|
||||||
<MotionPreviewClip
|
key={`${camera.name}-${range.start_time}-${range.end_time}-${preview?.src ?? "none"}-${idx}`}
|
||||||
cameraName={camera.name}
|
data-clip-id={clipId}
|
||||||
range={range}
|
ref={clipRef}
|
||||||
playbackRate={playbackRate}
|
>
|
||||||
preview={preview}
|
{isMounted ? (
|
||||||
fallbackFrameTimes={fallbackFrameTimes}
|
<MotionPreviewClip
|
||||||
motionHeatmap={motionHeatmap}
|
cameraName={camera.name}
|
||||||
nonMotionAlpha={nonMotionAlpha}
|
range={range}
|
||||||
isVisible={
|
playbackRate={playbackRate}
|
||||||
windowVisible &&
|
preview={preview}
|
||||||
(visibleClips.includes(
|
fallbackFrameTimes={fallbackFrameTimes}
|
||||||
`${camera.name}-${range.start_time}-${range.end_time}-${idx}`,
|
motionHeatmap={motionHeatmap}
|
||||||
) ||
|
nonMotionAlpha={nonMotionAlpha}
|
||||||
(!hasVisibilityData && idx < 8))
|
isVisible={
|
||||||
}
|
windowVisible &&
|
||||||
onSeek={onSeek}
|
(visibleClips.includes(clipId) ||
|
||||||
/>
|
(!hasVisibilityData && idx < 8))
|
||||||
</div>
|
}
|
||||||
),
|
onSeek={onSeek}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="aspect-video rounded-lg bg-black md:rounded-2xl" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user