From 43fbc1eff58b12a5889337dbd646b0f1a34770c0 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 19 Mar 2026 22:37:10 +0300 Subject: [PATCH] Fix modal-open layout shift caused by position:fixed scroll lock Replace the body position:fixed hack with overflow:hidden on html element, which works cleanly with scrollbar-gutter:stable to prevent layout shift. Co-Authored-By: Claude Opus 4.6 (1M context) --- server/src/wled_controller/static/css/base.css | 6 ++---- .../wled_controller/static/js/core/command-palette.ts | 4 ++-- server/src/wled_controller/static/js/core/ui.ts | 9 ++------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/server/src/wled_controller/static/css/base.css b/server/src/wled_controller/static/css/base.css index d278c24..9ff14c1 100644 --- a/server/src/wled_controller/static/css/base.css +++ b/server/src/wled_controller/static/css/base.css @@ -91,10 +91,8 @@ body { line-height: 1.6; } -body.modal-open { - position: fixed; - width: 100%; - overflow-y: scroll; /* keep scrollbar gutter to prevent layout shift */ +html.modal-open { + overflow: hidden; /* scrollbar-gutter: stable keeps the gutter reserved */ } /* ── Ambient animated background ── */ diff --git a/server/src/wled_controller/static/js/core/command-palette.ts b/server/src/wled_controller/static/js/core/command-palette.ts index 68ac122..668fdc6 100644 --- a/server/src/wled_controller/static/js/core/command-palette.ts +++ b/server/src/wled_controller/static/js/core/command-palette.ts @@ -296,7 +296,7 @@ export async function openCommandPalette() { const overlay = document.getElementById('command-palette')!; const input = document.getElementById('cp-input') as HTMLInputElement; overlay.style.display = ''; - document.body.classList.add('modal-open'); + document.documentElement.classList.add('modal-open'); input.value = ''; input.placeholder = t('search.placeholder'); _loading = true; @@ -318,7 +318,7 @@ export function closeCommandPalette() { _isOpen = false; const overlay = document.getElementById('command-palette')!; overlay.style.display = 'none'; - document.body.classList.remove('modal-open'); + document.documentElement.classList.remove('modal-open'); _items = []; _filtered = []; } diff --git a/server/src/wled_controller/static/js/core/ui.ts b/server/src/wled_controller/static/js/core/ui.ts index 5e52f78..e240d74 100644 --- a/server/src/wled_controller/static/js/core/ui.ts +++ b/server/src/wled_controller/static/js/core/ui.ts @@ -67,13 +67,10 @@ export function setupBackdropClose(modal: any, closeFn: () => void) { } let _lockCount = 0; -let _savedScrollY = 0; export function lockBody() { if (_lockCount === 0) { - _savedScrollY = window.scrollY; - document.body.style.top = `-${_savedScrollY}px`; - document.body.classList.add('modal-open'); + document.documentElement.classList.add('modal-open'); } _lockCount++; } @@ -82,9 +79,7 @@ export function unlockBody() { if (_lockCount <= 0) return; _lockCount--; if (_lockCount === 0) { - document.body.classList.remove('modal-open'); - document.body.style.top = ''; - window.scrollTo({ top: _savedScrollY, behavior: 'instant' }); + document.documentElement.classList.remove('modal-open'); } }