Last active
August 22, 2025 06:47
-
-
Save AshKyd/d3428f6c4e6cf2e3467f8ce432382ede to your computer and use it in GitHub Desktop.
Returns a value that's safe enough to use in iPadOS webview browsers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Due to assorted bugs in iPadOS webview browsers (Firefox, Brave), `vh` and `lvh` | |
* units change unpredictably and can cause content to shift. | |
* | |
* This utility provides an approximation of `100vh` in the browser. In most | |
* browsers it will be exactly `100vh`, but in Safari iPad browsers it will | |
* be clamped at either `100dvh` or `100vh` depending on the state of the browser | |
* address bar at the time. | |
* | |
* Use this to size elements in the page flow in a way that won't trigger | |
* content to shift when the address bar appears. | |
* | |
* @param {function(number, { type: "orientation" | "resize" }): void} onChange - | |
* A callback function that receives the newly measured height | |
* (in pixels) whenever the viewport height changes. This will be triggered on | |
* orientation change and, on non-iPad devices, also on resize events. | |
* | |
* @returns {number} The initial measured height (in pixels) representing 100vh. | |
* | |
* @see https://github.com/mozilla-mobile/firefox-ios/issues/22607 | |
*/ | |
function getViewportMaxHeightStore(onChange) { | |
const isClientSide = typeof window !== "undefined"; | |
let previousValue = 0; | |
if (!isClientSide) { | |
return 0; | |
} | |
const div = document.createElement("div"); | |
div.style = "height:10vh;position:absolute;left:0;top:0;width:1px;pointer-events:none;"; | |
document.body.appendChild(div); | |
function measure() { | |
if (!div) { | |
return 0; | |
} | |
const { height } = div.getBoundingClientRect(); | |
return height * 10; | |
} | |
screen.orientation.addEventListener("change", () => onChange(measure(), { type: "orientation" })); | |
const isIpad = | |
!!navigator.userAgent.match(/Macintosh.*AppleWebKit.*Safari/) && navigator.maxTouchPoints > 1; | |
const resizeObserver = new ResizeObserver(() => { | |
// BUG: Safari webviews on iPadOS are the cause of all our headaches. For | |
// those browsers, never recalculate height except on orientation change. | |
if (!previousValue || !isIpad) { | |
onChange(measure(), { type: "resize" }); | |
} | |
}); | |
resizeObserver.observe(div); | |
return measure(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment