Files
privacy.sexy/src/presentation/components/Shared/Modal/Hooks/ScrollLock/WindowScrollDomStateAccessor.ts
undergroundwires bc4879cfe9 Fix Chromium scrollbar-induced layout shifts
This commit addresses an issue in Chromium on Linux and Windows where
the appearance of a vertical scrollbar causes unexpected horizontal
layout shifts. This behavior typically occurs when the window is
resized, a card is opened or a script is selected, resulting in content
being pushed to the left.

The solution implemented involves using `scrollbar-gutter: stable` to
ensure space is always allocated for the scrollbar, thus preventing any
shift in the page layout. This fix primarily affects Chromium-based
browsers on Linux and Windows. It has no impact on Firefox on any
platform, or any browser on macOS (including Chromium). Because these
render the scrollbar as an overlay, and do not suffer from this issue.

Steps to reproduce the issue using Chromium browser on Linux/Windows:

1. Open the app with a height large enough where a vertical scrollbar is
   not visible.
2. Resize the window to a height that triggers a vertical scrollbar.
3. Notice the layout shift as the body content moves to the right.

Changes:

- Add a CSS mixin to handle scrollbar gutter allocation with a fallback.
- Add support for modal dialog background lock to handle
  `scrollbar-gutter: stable;` in calculations to avoid layout shift when
  a modal is open.
- Add E2E test to avoid regression.
- Update DevToolkit to accommodate new scrollbar spacing.
2024-05-09 18:35:02 +02:00

69 lines
2.4 KiB
TypeScript

import type { ScrollDomStateAccessor } from './ScrollDomStateAccessor';
const HtmlElement = document.documentElement;
const BodyElement = document.body;
export function getWindowDomState(): ScrollDomStateAccessor {
return new WindowScrollDomState();
}
class WindowScrollDomState implements ScrollDomStateAccessor {
get bodyStyleOverflowX(): string { return BodyElement.style.overflowX; }
set bodyStyleOverflowX(value: string) { BodyElement.style.overflowX = value; }
get bodyStyleOverflowY(): string { return BodyElement.style.overflowY; }
set bodyStyleOverflowY(value: string) { BodyElement.style.overflowY = value; }
get htmlScrollLeft(): number { return HtmlElement.scrollLeft; }
set htmlScrollLeft(value: number) { HtmlElement.scrollLeft = value; }
get htmlScrollTop(): number { return HtmlElement.scrollTop; }
set htmlScrollTop(value: number) { HtmlElement.scrollTop = value; }
get bodyStyleLeft(): string { return BodyElement.style.left; }
set bodyStyleLeft(value: string) { BodyElement.style.left = value; }
get bodyStyleTop(): string { return BodyElement.style.top; }
set bodyStyleTop(value: string) { BodyElement.style.top = value; }
get bodyStylePosition(): string { return BodyElement.style.position; }
set bodyStylePosition(value: string) { BodyElement.style.position = value; }
get bodyStyleWidth(): string { return BodyElement.style.width; }
set bodyStyleWidth(value: string) { BodyElement.style.width = value; }
get bodyStyleHeight(): string { return BodyElement.style.height; }
set bodyStyleHeight(value: string) { BodyElement.style.height = value; }
get bodyComputedMarginLeft(): string { return window.getComputedStyle(BodyElement).marginLeft; }
get bodyComputedMarginRight(): string { return window.getComputedStyle(BodyElement).marginRight; }
get bodyComputedMarginTop(): string { return window.getComputedStyle(BodyElement).marginTop; }
get bodyComputedMarginBottom(): string {
return window.getComputedStyle(BodyElement).marginBottom;
}
get htmlScrollWidth(): number { return HtmlElement.scrollWidth; }
get htmlScrollHeight(): number { return HtmlElement.scrollHeight; }
get htmlClientWidth(): number { return HtmlElement.clientWidth; }
get htmlClientHeight(): number { return HtmlElement.clientHeight; }
get htmlOffsetWidth(): number { return HtmlElement.offsetWidth; }
get htmlOffsetHeight(): number { return HtmlElement.offsetHeight; }
}