From 5c33cdba4eee549200c1134ccda40d1614627f62 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:14:32 -0600 Subject: [PATCH] Timeline performance improvements (#12070) * Use intersection observer for timeline segments * only render when visible --- web/src/components/timeline/EventSegment.tsx | 40 +++++++++++++ .../timeline/MotionReviewTimeline.tsx | 39 +------------ web/src/components/timeline/MotionSegment.tsx | 56 ++++++++++++++++--- 3 files changed, 88 insertions(+), 47 deletions(-) diff --git a/web/src/components/timeline/EventSegment.tsx b/web/src/components/timeline/EventSegment.tsx index 23d5c5f44..5ce8d65fa 100644 --- a/web/src/components/timeline/EventSegment.tsx +++ b/web/src/components/timeline/EventSegment.tsx @@ -8,6 +8,7 @@ import React, { useEffect, useMemo, useRef, + useState, } from "react"; import { HoverCard, @@ -195,9 +196,48 @@ export function EventSegment({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [startTimestamp]); + const [segmentRendered, setSegmentRendered] = useState(false); + const segmentObserverRef = useRef(null); + const segmentRef = useRef(null); + + useEffect(() => { + const segmentObserver = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && !segmentRendered) { + setSegmentRendered(true); + } + }, + { threshold: 0 }, + ); + + if (segmentRef.current) { + segmentObserver.observe(segmentRef.current); + } + + segmentObserverRef.current = segmentObserver; + + return () => { + if (segmentObserverRef.current) { + segmentObserverRef.current.disconnect(); + } + }; + }, [segmentRendered]); + + if (!segmentRendered) { + return ( +
+ ); + } + return (
(null); - useEffect(() => { - if (selectedTimelineRef.current && segments && isDesktop) { - segmentsObserver.current = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const segmentId = entry.target.getAttribute("data-segment-id"); - - const segmentElements = - internalTimelineRef.current?.querySelectorAll( - `[data-segment-id="${segmentId}"] .motion-segment`, - ); - segmentElements?.forEach((segmentElement) => { - segmentElement.classList.remove("hidden"); - segmentElement.classList.add("animate-in"); - }); - } - }); - }, - { threshold: 0 }, - ); - - // Get all segment divs and observe each one - const segmentDivs = - selectedTimelineRef.current.querySelectorAll(".segment.has-data"); - segmentDivs.forEach((segmentDiv) => { - segmentsObserver.current?.observe(segmentDiv); - }); - } - - return () => { - segmentsObserver.current?.disconnect(); - }; - }, [selectedTimelineRef, segments]); - return ( 0 ? "hidden" : ""} - zoom-in-[0.2] ${secondHalfSegmentWidth < 5 ? "duration-200" : "duration-1000"}`; - const animationClassesFirstHalf = `motion-segment ${firstHalfSegmentWidth > 0 ? "hidden" : ""} - zoom-in-[0.2] ${firstHalfSegmentWidth < 5 ? "duration-200" : "duration-1000"}`; - const severityColorsBg: { [key: number]: string } = { 1: reviewed ? "from-severity_significant_motion-dimmed/10 to-severity_significant_motion/10" @@ -162,6 +163,44 @@ export function MotionSegment({ } }, [segmentTime, setHandlebarTime]); + const [segmentRendered, setSegmentRendered] = useState(false); + const segmentObserverRef = useRef(null); + const segmentRef = useRef(null); + + useEffect(() => { + const segmentObserver = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && !segmentRendered) { + setSegmentRendered(true); + } + }, + { threshold: 0 }, + ); + + if (segmentRef.current) { + segmentObserver.observe(segmentRef.current); + } + + segmentObserverRef.current = segmentObserver; + + return () => { + if (segmentObserverRef.current) { + segmentObserverRef.current.disconnect(); + } + }; + }, [segmentRendered]); + + if (!segmentRendered) { + return ( +
+ ); + } + return ( <> {(((firstHalfSegmentWidth > 0 || secondHalfSegmentWidth > 0) && @@ -171,6 +210,7 @@ export function MotionSegment({