From f79e5621f83dbbd36e8baf7d8f9fe3ea1705fd1e Mon Sep 17 00:00:00 2001 From: Tomasz Plonka <4361591+PlkMarudny@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:37:27 +0300 Subject: [PATCH 1/2] feat: use transform() for playhead movement --- app.js | 4 +++- index.html | 2 +- style.css | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 23237d1..d9236f2 100644 --- a/app.js +++ b/app.js @@ -6305,7 +6305,9 @@ function loop(ts) { drawFrame(); } if (state.dirtyTimeline) rebuildClips(); - els.playhead.style.left = state.time * state.pps + "px"; + // device-pixel snap — CSS-pixel round still AA-pulses at 125%/150% DPR + const dpr = devicePixelRatio || 1; + els.playhead.style.transform = `translateX(${Math.round(state.time * state.pps * dpr) / dpr}px)`; drawRuler(); updateSafeOverlay(); updateKfGraphs(); diff --git a/index.html b/index.html index ba3ca98..69a54e2 100644 --- a/index.html +++ b/index.html @@ -162,7 +162,7 @@
-
+
diff --git a/style.css b/style.css index bc33913..920e581 100644 --- a/style.css +++ b/style.css @@ -1868,20 +1868,30 @@ body.track-size-s .clip .clip-kf-n { } /* ── Playhead ── */ +/* 13px wrapper (cap box) so we don't compositor-scale a 1px layer. Line at x=6. */ .playhead { position: absolute; top: 0; bottom: 0; - width: 1px; - background: #ff4d6a; + left: -6px; + width: 13px; z-index: 4; pointer-events: none; } +.playhead-line { + position: absolute; + top: 0; + bottom: 0; + left: 6px; + width: 1px; + background: #ff4d6a; +} + .playhead-cap { position: absolute; top: 0; - left: -6px; + left: 0; width: 13px; height: 14px; background: #ff4d6a; From 5bf9e9d8e1fa8812b57511e4f7653e2cd6e11b10 Mon Sep 17 00:00:00 2001 From: Tomasz Plonka <4361591+PlkMarudny@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:18:46 +0300 Subject: [PATCH 2/2] fix: restructure playhead movement mechanics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The layout shift from left was not the clips reflowing after all - the playhead expanding the scroller’s overflow was to blame. CLS shift 0.00 now. --- app.js | 9 ++++++--- index.html | 2 +- style.css | 21 ++++++++------------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/app.js b/app.js index d9236f2..d5f55db 100644 --- a/app.js +++ b/app.js @@ -6280,6 +6280,7 @@ function ensureFont(name) { /* ── Main loop ── */ let lastTs = null; +let playheadPx = -1; function loop(ts) { if (lastTs == null) lastTs = ts; const dt = Math.min(0.1, (ts - lastTs) / 1000); @@ -6305,9 +6306,11 @@ function loop(ts) { drawFrame(); } if (state.dirtyTimeline) rebuildClips(); - // device-pixel snap — CSS-pixel round still AA-pulses at 125%/150% DPR - const dpr = devicePixelRatio || 1; - els.playhead.style.transform = `translateX(${Math.round(state.time * state.pps * dpr) / dpr}px)`; + const phX = Math.round(state.time * state.pps); + if (phX !== playheadPx) { + playheadPx = phX; + els.playhead.style.left = phX + "px"; + } drawRuler(); updateSafeOverlay(); updateKfGraphs(); diff --git a/index.html b/index.html index 69a54e2..ba3ca98 100644 --- a/index.html +++ b/index.html @@ -162,7 +162,7 @@
-
+
diff --git a/style.css b/style.css index 920e581..cba587f 100644 --- a/style.css +++ b/style.css @@ -1542,6 +1542,7 @@ input[type=range] { .timeline-scroll { flex: 1; + min-height: 0; overflow-x: auto; overflow-y: auto; position: relative; @@ -1560,6 +1561,9 @@ input[type=range] { .tracks-content { position: relative; + /* clip so abspos playhead `left` cannot grow the scroller's overflow */ + overflow: clip; + overflow-clip-margin: 8px; } .work-dim { position: absolute; @@ -1868,30 +1872,21 @@ body.track-size-s .clip .clip-kf-n { } /* ── Playhead ── */ -/* 13px wrapper (cap box) so we don't compositor-scale a 1px layer. Line at x=6. */ .playhead { position: absolute; top: 0; bottom: 0; - left: -6px; - width: 13px; - z-index: 4; - pointer-events: none; -} - -.playhead-line { - position: absolute; - top: 0; - bottom: 0; - left: 6px; width: 1px; background: #ff4d6a; + z-index: 4; + pointer-events: none; + contain: layout; } .playhead-cap { position: absolute; top: 0; - left: 0; + left: -6px; width: 13px; height: 14px; background: #ff4d6a;