I Reinvented the Wheel — Kind of

Bhavesh Rawat
CodeX
Published in
6 min readMay 11, 2024
Hero Section of the Author’s website
‘’My Website’s Hero Section

Viewport height units are bliss, till you see them in action on mobile browsers.

Your website’s hero section is like your chance at making a good first impression on visitors within mere seconds. Well, you better make it the star of the show, and the easiest and foremost way to do that? Stick it in their face, don’t let anything else be seen. How we do that? One healthy way is to let the hero section take the entire user’s screen, also known as Viewport; just like I did, and many others do.

CSS made it easy to achieve this ‘entire screen’s height’ look. You would like to have the first fold of your webpage reserved for a section? Here, throw this ‘vh’ unit on that section, and you’re done. Now, this works flawlessly on desktop browsers, but the problem occurs when it comes to mobile browsers because of the sliding UI mechanism that these browsers have incorporated.

You guys noticed the content shift that happens when I scroll? So, basically as I scroll up, the bottom bar slides down, and now the ‘vh’ unit is compensating for that extra gained height. Now, I am aware of the new dynamic variants of the ‘vh’ units, and they do work. I tested them all, the browsers on Android makes the desired use of it. It gets finicky on iOS though. The above GIF is my website opened in Firefox on iOS. See how ugly that shift feels!?

I couldn’t live with this, might look like a small, insignificant thing to few but this kept me awake at night, and well, I ended up reinventing the wheel with a slight change.

While ‘vh’ keeps on updating itself on the viewport height change, I just needed the initial height of the window and wanted it to change only when the device’s orientation changes, and I could then easily plug that value to a variable and use it. In theory, it felt like a piece of cake, but there were some hurdles where I felt like screaming.

Now that I had a working of the logic in my mind, I started with implementation, knocked on the easiest part first, setting up the CSS property and the variable to use the value (--vh).

.hero { 
min-height: var(--vh, 100lvh);
}

The ‘100lvh’ here is used as fallback, if a browser has JavaScript disabled, and it serves for laptops and desktop browser, will tell you about this soon.

Now, comes the actual logic implementation part, the JavaScript. Let’s dive in, shall we. So, for starters, I wrote two functions, the first one gets the window’s height, and the second one that sets the value.

  function getWindowHeight() {
return window.innerHeight;
}
function setVhHeight(el, h) {
el.style.setProperty("--vh", `${h}px`);
}

Next, I straight up used that second function to get the window height, and set it as a CSS variable on the document so that the variable can be globally accessed. Now, comes the part that brought me into agony.

At the very first try, I used the ‘resize’ event listener on window to make the value update if the screen changes it’s device’s orientation, and I was caught dumbfounded a while later when I noticed that ‘resize’ was getting triggered even when the browser UI was sliding, and another thing that would have made me ditch it was that, in future, when I would have been working on the responsive mode of a section, that event would get spammed every time I would use the responsive mode. Well, ‘resize’ was out of the question.

I, then starting searching for the event that listens for the device orientation changes, and shortly came across the ‘orientationchange’ event. It was everything I needed but it was getting deprecated and wasn’t recommended for production. I decided to continue my search and then came across ScreenOrientation API, and shortly found out that it provides a ‘change’ event listener. The support for this API and event listener was not an issue at all.

const doc = document.documentElement;
setVhHeight(doc, getWindowHeight());
screen.orientation.addEventListener("change", () => {
console.log("orientation changed")
})

I starting using it, and dry ran it by logging something to the console, and it worked, so I used that second function again. It was a hit and a miss most of the time. It would work on the first rotation but would mess up in the second rotation, and sometimes it would work. I wasn’t satisfied, and ran back to Google to check whether it was finicky just for me or other faced it too, but nothing helpful came out.

I reverted back to the raw code and dry ran it again, and of course, it worked just fine. I then tried to access the orientation type and angle, and everything was correct. I got really confused as to why it wasn’t working, after tons of playing around, asking ChatGPT, nothing helped.

So, just out of spite, I put an alert before the function, and tried running it. As I rotated the device, alert dialog popped up, when I dismissed it, the height updated correctly and it worked great, I rotated it back to original, alert dialog popped up, and all went great. I went on and on like this for about 2–3 minutes, noticing it worked flawlessly, it looked like something was wrong with the execution of the code itself.

screen.orientation.addEventListener("change", () => {
alert("rotated")
setVhHeight(doc, getWindowHeight());
})

alert in JavaScript is a synchronous function which means that it blocks the execution of the further code until the user dismisses it. That gave me the understanding of what was going under the hood, and why the height was being calculated incorrectly.

Imagine you’re holding a phone and rotating it. The screen needs to rotate and show everything in the new direction. JavaScript quickly detects this rotation and send out a message saying “Hey, the device has changed orientation!”

Well, there’s a catch. Even though JavaScript sends the message quickly, the screen itself is still turning and rearranging things. If you try to measure the screen’s height right after the message is sent, you might get the wrong answer because the screen hasn’t finished adjusting yet.

An alert box, here, stops the code execution right before the function that calculates the height. This interruption helps! It gives the screen a chance to finish its rotation and when the alert prompt is handled, ‘setVhHeight’ function is executed which gives you the correct height. So, in a way, JavaScript can be a little too fast for itself in this situation, but an alert box puts it to ease.

So to put my theory into testing, I removed the alert and wrapped the ‘setVhHeight’ function in a ‘setTimeout’ function.

screen.orientation.addEventListener("change", () => {
setTimeout(() => {
setVhHeight(doc, getWindowHeight());
}, 100)
})

setTimeout’ function is an asynchronous function that delays event by a given time and it doesn’t block the main thread of execution. After delaying the ‘setVhHeight’ by 100ms, I was getting the desired outcome. The delay was enough to take new, more importantly, correct height into account but not much that it affects the visitor’s experience.

Now that I had fixed the main issue, there was one more thing to address, scope. I didn’t need this code to run at desktop devices where the ‘vh’ is reliable and works well. I needed this calculation to run at mobile devices. So, I wrapped the entire code in a media query and a navigator property.

if (!window.matchMedia("(hover: hover)").matches || navigator.userAgentData?.mobile) {
function getWindowHeight() {
return window.innerHeight;
}
function setVhHeight(el, h) {
el.style.setProperty("--vh", `${h}px`);
}
const doc = document.documentElement;
setVhHeight(doc, getWindowHeight());
screen.orientation.addEventListener("change", () => {
setTimeout(() => {
setVhHeight(doc, getWindowHeight());
}, 100)
})
}

Usually most mobile devices don’t have the hover ability, and JavaScript checks if the user device satisfies that condition, and if somehow that media query condition doesn’t work, the userAgentData property comes into play. (It is still at 74% browser support).

But, all in all I have tested my website on Desktop(Chrome, Firefox), and mobile devices (iOS, Android), and the media query condition hasn’t failed me.

I will be using this work around on all my personal and client’s projects till iOS doesn’t fix its viewport unit implementation. Hope you enjoyed this rant. Peace out!

I am not part of the Medium Partner Program because of demographics. If you like my stuff, support me here or get your Surfshark VPN from this link. Thanks:)

--

--

Bhavesh Rawat
CodeX
Writer for

22 • Frontend Engg. • Tech Enthusiast • Blogger • Curator