diff --git a/package.json b/package.json index 377f3d5d..7750a560 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.20.19", + "version": "0.20.20", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/js/scrollLocks.js b/src/js/scrollLocks.js index 9713a973..a43a93bd 100644 --- a/src/js/scrollLocks.js +++ b/src/js/scrollLocks.js @@ -9,6 +9,8 @@ function getHeaderElement(container) { } function lockScroll(container) { + // Important: read scrollY before pinning the header + const scrollY = window.scrollY; const header = getHeaderElement(container); let headerHeight = 0; if (header) { @@ -18,11 +20,11 @@ function lockScroll(container) { // https://stackoverflow.com/a/19667968 const main = container.querySelector("main"); if (main) { - const topMargin = -1 * (window.scrollY - headerHeight); + const topMargin = -1 * (scrollY - headerHeight); main.style.marginTop = topMargin + "px"; - // Non-zero padding keeps the compensation margin from collapsing - // with the first child's top margin - main.style.paddingTop = "0.05px"; + // flow-root keeps the compensation margin from collapsing with the + // first child's top margin + main.style.display = "flow-root"; } const body = document.body; body.style.position = "fixed"; @@ -55,7 +57,7 @@ function unlockScroll(container, { restoreScroll = true } = {}) { if (main) { scrollTo = -1 * (main.getBoundingClientRect().top - headerHeight); main.style.marginTop = ""; - main.style.paddingTop = ""; + main.style.display = ""; } if (header) { header.classList.remove("scroll-lock-pinned"); diff --git a/tests/unit/specs/scrollLock.test.js b/tests/unit/specs/scrollLock.test.js index 48a1879e..6bb7d43d 100644 --- a/tests/unit/specs/scrollLock.test.js +++ b/tests/unit/specs/scrollLock.test.js @@ -88,6 +88,30 @@ describe("ScrollLock", () => { assert.deepEqual(document.body.style.position, ""); }); + it("compensates with the scroll position from before the header is pinned", () => { + // Pinning the sticky header removes it from flow, and at the bottom of + // the page the browser clamps scrollY down by the header height — + // simulate that clamp to ensure the margin uses the pre-pin value. + const header = container.querySelector("header"); + const descriptor = Object.getOwnPropertyDescriptor(window, "scrollY"); + Object.defineProperty(window, "scrollY", { + configurable: true, + get: () => + header.classList.contains("scroll-lock-pinned") ? 3553.5 : 3600, + }); + try { + createLock(); + const main = container.querySelector("main"); + assert.deepEqual(main.style.marginTop, "-3600px"); + } finally { + if (descriptor) { + Object.defineProperty(window, "scrollY", descriptor); + } else { + delete window.scrollY; + } + } + }); + it("restores a locked scrollable ancestor's overflow on unlock", () => { const scrollable = document.createElement("div"); scrollable.style.overflowY = "auto";