Safari, being an Apple product, tends to “Think Different”. Given Apple’s dominant market share, especially in the US, it’s important that we understand these differences, and know how to work with them. In this blog post, we’re going to tackle the 100vh problem.
100vh
Quick recap. The vh unit, introduced with CSS3, allows you to specify the percent of the total viewport’s height. So 100vh would be 100% of the viewport. It's mobile responsive and has made building layouts much easier.
Navbar Problem
Let's try making a simple homepage with a footer for navigation.
In the main element is our content. We give it a height of 95vh or 95% of the total height. The footer gets the remaining 5% of the viewport. If we load this up on a desktop, it works exactly as expected.
Checking Safari on mobile and we see:
Why?
This must be a bug, right? Well, no. This is actually a feature.
When working on Safari, the team worked hard to show the button bar only when necessary. Their goal was to give more space to the website whenever possible, which is why the button bar hides when scrolling. This lead to a tough decision about how the viewport’s height would be handled. Would it:
1. change the height every time the bar hides and shows
or
2. make the viewport height constant, and have the button bar cover part of the viewport
They opted for a constant height to avoid jankiness. This means that our navbar is in fact there, just hidden behind the button bar. When the button bar hides, we can see the navbar really was there.
Navbar Solution
So what do we do? Instead of placing our navbar by using height, we use positioning instead.
Checking it out on desktop and we can see nothing changed:
And now on mobile, we finally get what we expect.
Content Problem
The navbar example is a bit contrived. Hopefully you wouldn’t use height for positioning regardless of the 100vh issue. A more likely scenario is that your content gets cut off as a result. Let’s consider the following:
Like before, this works as expected on a desktop.
And again, like before, we have an issue on iOS.
Content Solution
So how do we fix this? Well, unfortunately, it’s time to turn to JavaScript 😱
Agreed, using JavaScript to address a CSS issue is less than ideal. The problem is, what we really need for that height is the innerHeight since it changes as the button bar hides and appears.
Don’t fret, the code is simple. When the document content has loaded, we need to set the value of the height for each section to the window’s innerHeight. Additionally, whenever there is a resize event (e.g. the button bar disappears), we need to update the height again;
Which results in:
Great, it works, but it’s a bit janky. Well that’s the reality of whats going on here. That jankiness is the height of the element changing. Looking back at the decision the Safari team had to make about handling the viewport height, this is exactly the poor experience they wanted to avoid. Changing the height when the button bar hides and shows results in some unpleasant side-effects.
Conclusion
This wouldn’t be an issue if the button bar didn’t auto-hide, but Apple’s going to Apple, and over “magicify” everything.
Ok, so what do you do? Well, the least satisfying but likely best answer is that it’s up to you. Ideally you don’t find yourself in this situation. It’s likely a less than ideal user experience to have much content intentionally at the bottom of the view. I’m sure you can find additional workarounds (e.g. for React there’s Div100vh), but it’s worth keeping in mind that you are fighting with the browser at this point. A team of very smart engineers at Apple decided that the best route was to leave the viewport height a constant. I’d probably trust their judgment on this.