summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-07-17 13:38:16 +0200
committerMaze Winther <mazewinther@gmail.com>2025-07-17 13:38:16 +0200
commite577c9659f38eb9702f0c85a6bdcbc52d2720f2f (patch)
tree569f0314338f20c3bcfe11113bfcb6f274839d40 /apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx
parent8efc696dcddc94b1c248a099207ad97c932a545b (diff)
parentb1efc90157c87e93de422c900de74a3538040eed (diff)
downloadopencut-e577c9659f38eb9702f0c85a6bdcbc52d2720f2f.tar.gz
Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging
Diffstat (limited to 'apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx')
-rw-r--r--apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx61
1 files changed, 61 insertions, 0 deletions
diff --git a/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx b/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx
new file mode 100644
index 00000000..6537d909
--- /dev/null
+++ b/apps/web/src/components/editor/timeline/timeline-scroll-sync.tsx
@@ -0,0 +1,61 @@
+import { useEffect, useRef } from "react";
+
+export interface TimelineScrollSyncProps {
+ rulerScrollRef: React.RefObject<HTMLDivElement>;
+ tracksScrollRef: React.RefObject<HTMLDivElement>;
+}
+
+export function useTimelineScrollSync({
+ rulerScrollRef,
+ tracksScrollRef,
+}: TimelineScrollSyncProps) {
+ const isUpdatingRef = useRef(false);
+ const lastSyncRef = useRef(0);
+
+ // --- Horizontal scroll synchronization between ruler and tracks ---
+ 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;
+
+ if (!rulerViewport || !tracksViewport) return;
+
+ // Throttled scroll handlers for better performance
+ const handleRulerScroll = () => {
+ const now = Date.now();
+ if (isUpdatingRef.current || now - lastSyncRef.current < 16) return; // 60fps throttling
+ lastSyncRef.current = now;
+
+ isUpdatingRef.current = true;
+ tracksViewport.scrollLeft = rulerViewport.scrollLeft;
+ isUpdatingRef.current = false;
+ };
+
+ const handleTracksScroll = () => {
+ const now = Date.now();
+ if (isUpdatingRef.current || now - lastSyncRef.current < 16) return; // 60fps throttling
+ lastSyncRef.current = now;
+
+ isUpdatingRef.current = true;
+ rulerViewport.scrollLeft = tracksViewport.scrollLeft;
+ isUpdatingRef.current = false;
+ };
+
+ rulerViewport.addEventListener("scroll", handleRulerScroll, {
+ passive: true,
+ });
+ tracksViewport.addEventListener("scroll", handleTracksScroll, {
+ passive: true,
+ });
+
+ return () => {
+ rulerViewport.removeEventListener("scroll", handleRulerScroll);
+ tracksViewport.removeEventListener("scroll", handleTracksScroll);
+ };
+ }, [rulerScrollRef, tracksScrollRef]);
+
+ return null;
+}