refactor: enhance scroll synchronization for timeline and track labels

This commit is contained in:
Anwarul Islam
2025-07-11 03:15:35 +06:00
parent 9b78503562
commit 4d8760d0e1

View File

@ -119,9 +119,11 @@ export function Timeline() {
const tracksScrollRef = useRef<HTMLDivElement>(null); const tracksScrollRef = useRef<HTMLDivElement>(null);
const trackLabelsRef = useRef<HTMLDivElement>(null); const trackLabelsRef = useRef<HTMLDivElement>(null);
const playheadRef = useRef<HTMLDivElement>(null); const playheadRef = useRef<HTMLDivElement>(null);
const trackLabelsScrollRef = useRef<HTMLDivElement>(null);
const isUpdatingRef = useRef(false); const isUpdatingRef = useRef(false);
const lastRulerSync = useRef(0); const lastRulerSync = useRef(0);
const lastTracksSync = useRef(0); const lastTracksSync = useRef(0);
const lastVerticalSync = useRef(0);
// Timeline playhead ruler handlers // Timeline playhead ruler handlers
const { handleRulerMouseDown, isDraggingRuler } = useTimelinePlayheadRuler({ const { handleRulerMouseDown, isDraggingRuler } = useTimelinePlayheadRuler({
@ -608,7 +610,13 @@ export function Timeline() {
const tracksViewport = tracksScrollRef.current?.querySelector( const tracksViewport = tracksScrollRef.current?.querySelector(
"[data-radix-scroll-area-viewport]" "[data-radix-scroll-area-viewport]"
) as HTMLElement; ) as HTMLElement;
const trackLabelsViewport = trackLabelsScrollRef.current?.querySelector(
"[data-radix-scroll-area-viewport]"
) as HTMLElement;
if (!rulerViewport || !tracksViewport) return; if (!rulerViewport || !tracksViewport) return;
// Horizontal scroll synchronization between ruler and tracks
const handleRulerScroll = () => { const handleRulerScroll = () => {
const now = Date.now(); const now = Date.now();
if (isUpdatingRef.current || now - lastRulerSync.current < 16) return; if (isUpdatingRef.current || now - lastRulerSync.current < 16) return;
@ -625,8 +633,48 @@ export function Timeline() {
rulerViewport.scrollLeft = tracksViewport.scrollLeft; rulerViewport.scrollLeft = tracksViewport.scrollLeft;
isUpdatingRef.current = false; isUpdatingRef.current = false;
}; };
rulerViewport.addEventListener("scroll", handleRulerScroll); rulerViewport.addEventListener("scroll", handleRulerScroll);
tracksViewport.addEventListener("scroll", handleTracksScroll); tracksViewport.addEventListener("scroll", handleTracksScroll);
// Vertical scroll synchronization between track labels and tracks content
if (trackLabelsViewport) {
const handleTrackLabelsScroll = () => {
const now = Date.now();
if (isUpdatingRef.current || now - lastVerticalSync.current < 16)
return;
lastVerticalSync.current = now;
isUpdatingRef.current = true;
tracksViewport.scrollTop = trackLabelsViewport.scrollTop;
isUpdatingRef.current = false;
};
const handleTracksVerticalScroll = () => {
const now = Date.now();
if (isUpdatingRef.current || now - lastVerticalSync.current < 16)
return;
lastVerticalSync.current = now;
isUpdatingRef.current = true;
trackLabelsViewport.scrollTop = tracksViewport.scrollTop;
isUpdatingRef.current = false;
};
trackLabelsViewport.addEventListener("scroll", handleTrackLabelsScroll);
tracksViewport.addEventListener("scroll", handleTracksVerticalScroll);
return () => {
rulerViewport.removeEventListener("scroll", handleRulerScroll);
tracksViewport.removeEventListener("scroll", handleTracksScroll);
trackLabelsViewport.removeEventListener(
"scroll",
handleTrackLabelsScroll
);
tracksViewport.removeEventListener(
"scroll",
handleTracksVerticalScroll
);
};
}
return () => { return () => {
rulerViewport.removeEventListener("scroll", handleRulerScroll); rulerViewport.removeEventListener("scroll", handleRulerScroll);
tracksViewport.removeEventListener("scroll", handleTracksScroll); tracksViewport.removeEventListener("scroll", handleTracksScroll);
@ -920,24 +968,21 @@ export function Timeline() {
className="w-48 flex-shrink-0 border-r bg-panel-accent overflow-y-auto" className="w-48 flex-shrink-0 border-r bg-panel-accent overflow-y-auto"
data-track-labels data-track-labels
> >
<div className="flex flex-col gap-1"> <ScrollArea className="w-full h-full" ref={trackLabelsScrollRef}>
{tracks.map((track) => ( <div className="flex flex-col gap-1">
<div {tracks.map((track) => (
key={track.id} <div
className="flex items-center px-3 border-b border-muted/30 group bg-foreground/5" key={track.id}
style={{ height: `${getTrackHeight(track.type)}px` }} className="flex items-center px-3 border-b border-muted/30 group bg-foreground/5"
> style={{ height: `${getTrackHeight(track.type)}px` }}
<div className="flex items-center flex-1 min-w-0"> >
<TrackIcon track={track} /> <div className="flex items-center flex-1 min-w-0">
<TrackIcon track={track} />
</div>
</div> </div>
{track.muted && ( ))}
<span className="ml-2 text-xs text-red-500 font-semibold flex-shrink-0"> </div>
Muted </ScrollArea>
</span>
)}
</div>
))}
</div>
</div> </div>
)} )}