diff --git a/app/hooks/useTimeline.ts b/app/hooks/useTimeline.ts index 5534791..d991f5b 100644 --- a/app/hooks/useTimeline.ts +++ b/app/hooks/useTimeline.ts @@ -82,10 +82,13 @@ export const useTimeline = () => { }, [zoomLevel]); // Zoom functions that update scrubber positions and widths accordingly - const handleZoomIn = useCallback(() => { + // Zoom is relative to the center point (ruler position) so that the timeline + // zooms towards/away from the current playhead position + const handleZoomIn = useCallback((centerPx: number = 0) => { const currentZoom = zoomLevelRef.current; const newZoom = Math.min(MAX_ZOOM, currentZoom * 1.5); const zoomRatio = newZoom / currentZoom; + const offset = centerPx * (1 - zoomRatio); zoomLevelRef.current = newZoom; setZoomLevel(newZoom); @@ -96,17 +99,18 @@ export const useTimeline = () => { ...track, scrubbers: track.scrubbers.map((scrubber) => ({ ...scrubber, - left: scrubber.left * zoomRatio, + left: scrubber.left * zoomRatio + offset, width: scrubber.width * zoomRatio, })), })), })); }, []); - const handleZoomOut = useCallback(() => { + const handleZoomOut = useCallback((centerPx: number = 0) => { const currentZoom = zoomLevelRef.current; const newZoom = Math.max(MIN_ZOOM, currentZoom / 1.5); const zoomRatio = newZoom / currentZoom; + const offset = centerPx * (1 - zoomRatio); zoomLevelRef.current = newZoom; setZoomLevel(newZoom); @@ -117,17 +121,18 @@ export const useTimeline = () => { ...track, scrubbers: track.scrubbers.map((scrubber) => ({ ...scrubber, - left: scrubber.left * zoomRatio, + left: scrubber.left * zoomRatio + offset, width: scrubber.width * zoomRatio, })), })), })); }, []); - const handleZoomReset = useCallback(() => { + const handleZoomReset = useCallback((centerPx: number = 0) => { const currentZoom = zoomLevelRef.current; const newZoom = DEFAULT_ZOOM; const zoomRatio = newZoom / currentZoom; + const offset = centerPx * (1 - zoomRatio); zoomLevelRef.current = newZoom; setZoomLevel(newZoom); @@ -138,7 +143,7 @@ export const useTimeline = () => { ...track, scrubbers: track.scrubbers.map((scrubber) => ({ ...scrubber, - left: scrubber.left * zoomRatio, + left: scrubber.left * zoomRatio + offset, width: scrubber.width * zoomRatio, })), })), diff --git a/app/routes/home.tsx b/app/routes/home.tsx index fa2c737..c9c9d25 100644 --- a/app/routes/home.tsx +++ b/app/routes/home.tsx @@ -660,9 +660,9 @@ export default function TimelineEditor() { const scrollDirection = e.deltaY > 0 ? -1 : 1; if (scrollDirection > 0) { - handleZoomIn(); + handleZoomIn(rulerPositionPx); } else { - handleZoomOut(); + handleZoomOut(rulerPositionPx); } } }; @@ -673,7 +673,7 @@ export default function TimelineEditor() { return () => { timelineContainer.removeEventListener("wheel", handleWheel); }; - }, [handleZoomIn, handleZoomOut]); + }, [handleZoomIn, handleZoomOut, rulerPositionPx]); const { user, isLoading: isAuthLoading, isSigningIn, signInWithGoogle, signOut } = useAuth(); @@ -924,7 +924,7 @@ export default function TimelineEditor() {