From 07fa9704f55e8aa8287816135e0884f9df842bde Mon Sep 17 00:00:00 2001 From: Wassim Gharbi Date: Wed, 5 Aug 2026 21:33:37 -0700 Subject: [PATCH] Pause timeline playhead following once the user pans or zooms Following owned scrollLeft outright, so a pan or wheel-zoom during playback was undone on the next frame. Any scroll position the follow effect did not write is the user's; leave the window alone until the next play/pause. Co-Authored-By: Claude Opus 5 (1M context) --- components/Timeline.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/components/Timeline.tsx b/components/Timeline.tsx index 52837bd..11c3264 100644 --- a/components/Timeline.tsx +++ b/components/Timeline.tsx @@ -359,14 +359,23 @@ export default function Timeline() { dark, ]); + // Panning or zooming during playback hands the window to the user until the + // next play/pause: `scrollLeft` the follow effect did not write is theirs. + const userScrolledRef = useRef(false); + const autoScrollRef = useRef(null); + useEffect(() => { + userScrolledRef.current = false; + }, [playing]); + // Keep the playhead visible while playing. useEffect(() => { - if (!playing) return; + if (!playing || userScrolledRef.current) return; const el = scrollRef.current; if (!el) return; const px = currentTime * pps; if (px < el.scrollLeft + 24 || px > el.scrollLeft + width - 96) { el.scrollLeft = Math.max(0, px - 96); + autoScrollRef.current = el.scrollLeft; } }, [currentTime, playing, pps, width]); @@ -794,7 +803,13 @@ export default function Timeline() {
setScrollLeft(e.currentTarget.scrollLeft)} + onScroll={(e) => { + const next = e.currentTarget.scrollLeft; + if (autoScrollRef.current == null || Math.abs(next - autoScrollRef.current) > 1) + userScrolledRef.current = true; + autoScrollRef.current = null; + setScrollLeft(next); + }} onPointerDown={onBackgroundPointerDown} onPointerMove={onPointerMove} onPointerLeave={onPointerLeave}