How and Why I Built: vh-fix
Learning to program by scratching my own itch
While building my first portfolio site, I iterated through several versions to improve the user experience in subtle but practical ways. As I iterated, I realized that my designs all made heavy use of viewport heights to focus on content while hinting at more content, if available:
I believe this usage of viewport real estate helped my portfolio site focus on one thing at a time while leading users to more available content without <p>’s like “See projects below” or “The End”. Thankfully, a CSS unit helped make this possible: vh.
Enter vh units
Within a few second of googling “CSS viewport height”, vh units entered my life accompanied by a holy choir and golden confetti. As a viewport-relative unit, I could rest assured that vh units applied to all devices with near complete support in the major browsers. vh units represent a percentage of the full, current viewport height; that is, 1vh means 1% of the full, current viewport height. So for a time, everything seemed good and my portfolio looked just how I wanted it to. Until I tried it on Chrome on my iPhone.
Imagine scrolling some direction on a webpage and the webpage moving in the same direction. That’s how life is supposed to work. Now imagine if the webpage moved in the opposite direction at seemingly random times. That’s not how life is supposed to work but that’s what you experience when you use vh units in iOS Chrome. Unfortunately, “near complete support” for vh units does not include iOS Chrome due to how it handles the viewport’s inner height.
iOS Chrome has a topmost address bar with a dynamic height; if you scroll down, the bar will gradually minimize and if you scroll up, the bar will gradually maximize. This is crucial because the viewport’s inner height is roughly calculated as follows:
viewport inner height = screen height - address bar height
So in order, this is what goes down when you use iOS Chrome and scroll down on a webpage using vh units:
- The address bar gradually minimizes.
- The viewport’s inner height gradually increases.
- All elements with vh units recalculate their height based on the full, current viewport height.
So when you scroll down, elements with vh units will grow taller until the address bar is minimized, outrunning your scrolling finger and effectively negating the feedback of scrolling down. And when you scroll up, elements will grow shorter until the address bar is maximized, negating the feedback of scrolling up.
In both situations, it feels and looks like the webpage is doing the exact opposite of what you expect, which is the hallmark of a bad user experience. A user interface must strive to be a natural extension of a user’s expectations and experiences so much so that it dissipates and lets the experience take center stage. It shouldn’t hog the spotlight and certainly not by being unpleasant.
iOS Chrome’s dynamic-height address bar is a feature, not a bug; the root of this problem is how the viewport’s inner height is calculated based on the address bar’s height. Instead, the viewport’s height should be calculated independently and set to a static height. Thankfully, this behavior is becoming increasingly standard as seen in Android Chrome recently and with iOS Safari illustrated below:
This static viewport height retains the improved user experience of dynamic address bars / footers while preventing vh elements from resizing on different scroll directions. It’s a beautiful solution that just works. So I stole it and implemented it in vh-fix: an easy to use library that makes vh units work in iOS Chrome. All it requires is that it’s included in a webpage and that elements with vh units adopt the class, “vh-fix”.
How vh-fix works
- Detect if the browser is iOS Chrome.
- If it isn’t, do nothing; if it is, continue.
- Calculate the viewport height for portrait and landscape orientations.
- For all elements with class “vh-fix”, set a static height based on their vh value and viewport height.
- vh-fix wants to be painless. Upon its first version, it just required two things: to be included in a web page and for elements with vh units to adopt the class name, “vh-fix”. I hope to make it even easier to use by removing that second step.
- vh-fix wants to be an easy choice. As my first project, it initially didn’t have any tests and so that’s something I’m itching to add. vh-fix should offer peace of mind and be easily found on npm, so I need to learn how to publish it as an easily consumable package.
Conclusion and Credits
vh units are dope; they (almost) painlessly enable designs to optimize the content that is and isn’t currently in view. Albeit they have near complete support in modern browsers, there are still a few last frontiers to conquer like in iOS Chrome. In these edge cases, vh unit-based layouts are currently poor choices since each individual element resizes and compounds the problem.
Thankfully, there are many options for dealing with this such as vh-fix. And if it doesn’t solve your problems, please consider other great solutions such as: @christinechanyc’s vh-for-mobile, stanko’s mobile-chrome-vh-fix, thebuilder’s postcss-vh-to-px, and idibidiart’s react-native-viewport-units-fixed.
At the end of the day, hopefully vh-fix won’t have to exist and something similar will be iOS Chrome’s default behavior. But until then, if you have any suggestions or would like to contribute, please do! I would love to work with you and make vh-fix a painless, weightless, and easy choice for working with vh units. The source code is available on GitHub: raimanadh/vh-fix and you can visit its little website here.
If you enjoyed this article, please consider holding down the clap button to give it some claps 👏. You can also follow me on Twitter.
If you didn’t, feel free to leave a comment below and tell me why so I can improve myself and my content. Thank you for reading! ❤️