mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|||
|
|
|
||
|
|
export interface TimelineScrollSyncProps {
|
||
|
|
rulerScrollRef: React.RefObject<HTMLDivElement>;
|
||
|
|
tracksScrollRef: React.RefObject<HTMLDivElement>;
|
||
|
|
trackLabelsScrollRef: React.RefObject<HTMLDivElement>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useTimelineScrollSync({
|
||
|
|
rulerScrollRef,
|
||
|
|
tracksScrollRef,
|
||
|
|
trackLabelsScrollRef,
|
||
|
|
}: TimelineScrollSyncProps) {
|
||
|
|
const isUpdatingRef = useRef(false);
|
||
|
|
const lastRulerSync = useRef(0);
|
||
|
|
const lastTracksSync = useRef(0);
|
||
|
|
const lastVerticalSync = useRef(0);
|
||
|
|
|
||
|
|
// --- Scroll synchronization effect ---
|
||
|
|
useEffect(() => {
|
||
|
|
const rulerViewport = rulerScrollRef.current?.querySelector(
|
||
|
|
"[data-radix-scroll-area-viewport]"
|
||
|
|
) as HTMLElement;
|
||
|
|
const tracksViewport = tracksScrollRef.current?.querySelector(
|
||
|
|
"[data-radix-scroll-area-viewport]"
|
||
|
|
) as HTMLElement;
|
||
|
|
const trackLabelsViewport = trackLabelsScrollRef.current?.querySelector(
|
||
|
|
"[data-radix-scroll-area-viewport]"
|
||
|
|
) as HTMLElement;
|
||
|
|
|
||
|
|
if (!rulerViewport || !tracksViewport) return;
|
||
|
|
|
||
|
|
// Horizontal scroll synchronization between ruler and tracks
|
||
|
|
const handleRulerScroll = () => {
|
||
|
|
const now = Date.now();
|
||
|
|
if (isUpdatingRef.current || now - lastRulerSync.current < 16) return;
|
||
|
|
lastRulerSync.current = now;
|
||
|
|
isUpdatingRef.current = true;
|
||
|
|
tracksViewport.scrollLeft = rulerViewport.scrollLeft;
|
||
|
|
isUpdatingRef.current = false;
|
||
|
|
};
|
||
|
|
const handleTracksScroll = () => {
|
||
|
|
const now = Date.now();
|
||
|
|
if (isUpdatingRef.current || now - lastTracksSync.current < 16) return;
|
||
|
|
lastTracksSync.current = now;
|
||
|
|
isUpdatingRef.current = true;
|
||
|
|
rulerViewport.scrollLeft = tracksViewport.scrollLeft;
|
||
|
|
isUpdatingRef.current = false;
|
||
|
|
};
|
||
|
|
|
||
|
|
rulerViewport.addEventListener("scroll", handleRulerScroll);
|
||
|
|
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 () => {
|
||
|
|
rulerViewport.removeEventListener("scroll", handleRulerScroll);
|
||
|
|
tracksViewport.removeEventListener("scroll", handleTracksScroll);
|
||
|
|
};
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|