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) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 22:37:10 +03:00
parent 997ff2fd70
commit 43fbc1eff5
3 changed files with 6 additions and 13 deletions

View File

@@ -91,10 +91,8 @@ body {
line-height: 1.6; line-height: 1.6;
} }
body.modal-open { html.modal-open {
position: fixed; overflow: hidden; /* scrollbar-gutter: stable keeps the gutter reserved */
width: 100%;
overflow-y: scroll; /* keep scrollbar gutter to prevent layout shift */
} }
/* ── Ambient animated background ── */ /* ── Ambient animated background ── */

View File

@@ -296,7 +296,7 @@ export async function openCommandPalette() {
const overlay = document.getElementById('command-palette')!; const overlay = document.getElementById('command-palette')!;
const input = document.getElementById('cp-input') as HTMLInputElement; const input = document.getElementById('cp-input') as HTMLInputElement;
overlay.style.display = ''; overlay.style.display = '';
document.body.classList.add('modal-open'); document.documentElement.classList.add('modal-open');
input.value = ''; input.value = '';
input.placeholder = t('search.placeholder'); input.placeholder = t('search.placeholder');
_loading = true; _loading = true;
@@ -318,7 +318,7 @@ export function closeCommandPalette() {
_isOpen = false; _isOpen = false;
const overlay = document.getElementById('command-palette')!; const overlay = document.getElementById('command-palette')!;
overlay.style.display = 'none'; overlay.style.display = 'none';
document.body.classList.remove('modal-open'); document.documentElement.classList.remove('modal-open');
_items = []; _items = [];
_filtered = []; _filtered = [];
} }

View File

@@ -67,13 +67,10 @@ export function setupBackdropClose(modal: any, closeFn: () => void) {
} }
let _lockCount = 0; let _lockCount = 0;
let _savedScrollY = 0;
export function lockBody() { export function lockBody() {
if (_lockCount === 0) { if (_lockCount === 0) {
_savedScrollY = window.scrollY; document.documentElement.classList.add('modal-open');
document.body.style.top = `-${_savedScrollY}px`;
document.body.classList.add('modal-open');
} }
_lockCount++; _lockCount++;
} }
@@ -82,9 +79,7 @@ export function unlockBody() {
if (_lockCount <= 0) return; if (_lockCount <= 0) return;
_lockCount--; _lockCount--;
if (_lockCount === 0) { if (_lockCount === 0) {
document.body.classList.remove('modal-open'); document.documentElement.classList.remove('modal-open');
document.body.style.top = '';
window.scrollTo({ top: _savedScrollY, behavior: 'instant' });
} }
} }