refactor: enhance scroll synchronization for timeline and track labels
This commit is contained in:
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user