summaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/constants/timeline-constants.tsx1
-rw-r--r--apps/web/src/hooks/timeline/use-timeline-zoom.ts48
2 files changed, 40 insertions, 9 deletions
diff --git a/apps/web/src/constants/timeline-constants.tsx b/apps/web/src/constants/timeline-constants.tsx
index e467de41..d6af3c3a 100644
--- a/apps/web/src/constants/timeline-constants.tsx
+++ b/apps/web/src/constants/timeline-constants.tsx
@@ -39,6 +39,7 @@ export const TIMELINE_CONSTANTS = {
ZOOM_MIN: 0.1,
ZOOM_MAX: 100,
ZOOM_BUTTON_FACTOR: 1.7,
+ ZOOM_ANCHOR_PLAYHEAD_THRESHOLD: 0.15,
} as const;
export const DEFAULT_TIMELINE_VIEW_STATE: TTimelineViewState = {
diff --git a/apps/web/src/hooks/timeline/use-timeline-zoom.ts b/apps/web/src/hooks/timeline/use-timeline-zoom.ts
index 3d15b5c8..13024103 100644
--- a/apps/web/src/hooks/timeline/use-timeline-zoom.ts
+++ b/apps/web/src/hooks/timeline/use-timeline-zoom.ts
@@ -9,6 +9,7 @@ import {
} from "react";
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { useEditor } from "@/hooks/use-editor";
+import { zoomToSlider } from "@/lib/timeline/zoom-utils";
interface UseTimelineZoomProps {
containerRef: RefObject<HTMLDivElement | null>;
@@ -122,18 +123,47 @@ export function useTimelineZoom({
if (previousZoom === zoomLevel) return;
const scrollElement = tracksScrollRef.current;
- if (scrollElement) {
- editor.project.setTimelineViewState({
- viewState: {
- zoomLevel,
- scrollLeft: scrollElement.scrollLeft,
- playheadTime: editor.playback.getCurrentTime(),
- },
- });
+ if (!scrollElement) {
+ previousZoomRef.current = zoomLevel;
+ return;
+ }
+
+ const currentScrollLeft = scrollElement.scrollLeft;
+ const playheadTime = editor.playback.getCurrentTime();
+ const sliderPercent = zoomToSlider({ zoomLevel, minZoom });
+
+ if (sliderPercent >= TIMELINE_CONSTANTS.ZOOM_ANCHOR_PLAYHEAD_THRESHOLD) {
+ const playheadPixelsBefore =
+ playheadTime * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * previousZoom;
+ const playheadPixelsAfter =
+ playheadTime * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel;
+
+ const viewportOffset = playheadPixelsBefore - currentScrollLeft;
+ const newScrollLeft = playheadPixelsAfter - viewportOffset;
+
+ const maxScrollLeft =
+ scrollElement.scrollWidth - scrollElement.clientWidth;
+ const clampedScrollLeft = Math.max(
+ 0,
+ Math.min(maxScrollLeft, newScrollLeft),
+ );
+
+ scrollElement.scrollLeft = clampedScrollLeft;
+ if (rulerScrollRef.current) {
+ rulerScrollRef.current.scrollLeft = clampedScrollLeft;
+ }
}
previousZoomRef.current = zoomLevel;
- }, [zoomLevel, editor, tracksScrollRef]);
+
+ editor.project.setTimelineViewState({
+ viewState: {
+ zoomLevel,
+ scrollLeft: scrollElement.scrollLeft,
+ playheadTime,
+ },
+ });
+ }, [zoomLevel, editor, tracksScrollRef, rulerScrollRef, minZoom]);
// biome-ignore lint/correctness/useExhaustiveDependencies: tracksScrollRef is a stable ref
const saveScrollPosition = useCallback(() => {