UX: Improve sidebar positioning in Safari (#30888)

- Calculate the overscroll amount and subtract it from viewport-based
measurements

- Only round at the end of calculations, to avoid sub-pixel rounding
discrepancies

- Compensate for < 1px fluctuations which happen during scroll

Before: https://github.com/user-attachments/assets/a1044405-9f4a-46b1-a3b1-bc5fff29bf45

After: https://github.com/user-attachments/assets/212e4a32-aa97-4054-aba0-b7f1d993f007
This commit is contained in:
David Taylor 2025-01-21 09:43:30 +00:00 committed by GitHub
parent 4a7b98ef6d
commit 4933cfd46c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -90,16 +90,28 @@ export default class GlimmerSiteHeader extends Component {
return; return;
} }
// clamping to 0 to prevent negative values (hello, Safari) // We expect this to be zero, but when overscrolling in Safari it can have a non-zero value:
const headerWrapBottom = Math.max( const overscrollPx = Math.max(
0, 0,
Math.floor(this._headerWrap.getBoundingClientRect().bottom) document.documentElement.getBoundingClientRect().top
); );
const headerWrapTop =
this._headerWrap.getBoundingClientRect().top - overscrollPx;
let headerWrapBottom =
this._headerWrap.getBoundingClientRect().bottom - overscrollPx;
// While scrolling on iOS, an element fixed to the top of the screen can have a `top` which fluctuates
// between -1 and 1. To avoid that fluctuation affecting the header offset, we subtract that tiny fluctuation from the bottom value.
if (Math.abs(headerWrapTop) < 1) {
headerWrapBottom -= headerWrapTop;
}
let mainOutletOffsetTop = Math.max( let mainOutletOffsetTop = Math.max(
0, 0,
Math.floor(this._mainOutletWrapper.getBoundingClientRect().top) - this._mainOutletWrapper.getBoundingClientRect().top -
headerWrapBottom headerWrapBottom -
overscrollPx
); );
if (DEBUG && isTesting()) { if (DEBUG && isTesting()) {
@ -111,16 +123,19 @@ export default class GlimmerSiteHeader extends Component {
} }
const docStyle = document.documentElement.style; const docStyle = document.documentElement.style;
const currentHeaderOffset = const currentHeaderOffset =
parseInt(docStyle.getPropertyValue("--header-offset"), 10) || 0; parseInt(docStyle.getPropertyValue("--header-offset"), 10) || 0;
const newHeaderOffset = headerWrapBottom; const newHeaderOffset = Math.floor(headerWrapBottom);
if (currentHeaderOffset !== newHeaderOffset) { if (currentHeaderOffset !== newHeaderOffset) {
docStyle.setProperty("--header-offset", `${newHeaderOffset}px`); docStyle.setProperty("--header-offset", `${newHeaderOffset}px`);
} }
const currentMainOutletOffset = const currentMainOutletOffset =
parseInt(docStyle.getPropertyValue("--main-outlet-offset"), 10) || 0; parseInt(docStyle.getPropertyValue("--main-outlet-offset"), 10) || 0;
const newMainOutletOffset = headerWrapBottom + mainOutletOffsetTop; const newMainOutletOffset = Math.floor(
headerWrapBottom + mainOutletOffsetTop
);
if (currentMainOutletOffset !== newMainOutletOffset) { if (currentMainOutletOffset !== newMainOutletOffset) {
docStyle.setProperty("--main-outlet-offset", `${newMainOutletOffset}px`); docStyle.setProperty("--main-outlet-offset", `${newMainOutletOffset}px`);
} }