100vh in Safari on iOS

When working with Viewport Units there’s this longstanding and extremely annoying bug in Safari on iOS where it does not play nice with the vh unit. Setting a container to 100vh for example will actually result in an element that’s a wee bit too high: MobileSafari ignores parts of its UI when calculating 100vh.


Image by Max Schmitt

🤔 New to Viewport Units? Ahmad Shadeed has got you covered.

As Apple basically gives us the finger on this – stating that it works as intended (which we all can disagree on) – we have to rely on workarounds. In the past I’ve used like Viewport Units Buggyfill or Louis Hoebregts’ CSS Custom Properties Hack to fix this behavior. I was glad to see that Matt Smith recently found a way to have MobileSafari render an element at 100vh using CSS:

As I replied on Twitter: Nice, but I’d rather have MobileSafari fix the vh unit, as using -webkit-fill-available for this will only work to achieving 100vh.

If you want to achieve a perfect 50vh for example, using -webkit-fill-available won’t work as you can’t use -webkit-fill-available in calc(). Above that it won’t work when the targeted element is nested somewhere deep in your DOM tree with one its parents already having a height set.

Come ‘on Safari, stop being the new IE6 …

UPDATE 2020.05.16 Apparently this -webkit-fill-available workaround can negatively impact the Chrome browser:

Given this I guess the recommended workaround right now still remains Louis Hoebregts’ CSS Custom Properties Hack:

.my-element {
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
}
const setVh = () => {
  const vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
};

window.addEventListener('load', setVh);
window.addEventListener('resize', setVh);

Making viewport units work properly in Mobile Safari

A typical issue with the well supported Viewport Relative Units (you know: vh, vw, vmin, and vmax) that bothers me a lot is that MobileSafari (Safari on iOS) takes the height of the address bar into account for 100vh.

Take a look at the footer of that first block in the screenshot below: since its container exceeds 100% of the viewport’s height – even though said container is set to be 100vh in height – the date at the bottom bleeds out of the viewport:

viewport-units-buggyfill-without

Viewport Units Buggyfill is a script that fixes that kind of bad browser implementations. With Viewport Units Buggyfill applied, all is fine and dandy:

viewport-units-buggyfill-with

Next to initializing the script on load, on also needs to listen for the resize event in case – for example – the tabs bar get shown/hidden.

import * as viewportUnitsBuggyfill from 'viewport-units-buggyfill';

// …

// Initialize viewportUnitsBuggyfill
viewportUnitsBuggyfill.init();

// Also hook viewportUnitsBuggyfill to resize event (if it was initialized)
if (document.getElementById('patched-viewport')) {
    window.addEventListener('resize', viewportUnitsBuggyfill.refresh, true);
}

Viewport Units Buggyfill →

Did this help you out? Like what you see?
Consider donating.

I don’t run ads on my blog nor do I do this for profit. A donation however would always put a smile on my face though. Thanks!

☕️ Buy me a Coffee ($3)

Prevent overscroll/bounce in iOS MobileSafari and Chrome (CSS only)

UPDATE 2017.12: For non-Safari browsers you can use overscroll-behavior to solve exactly this. Simply apply overscroll-behavior-y: none; on html, body and be done with it.

Safari however still requires the workaround detailed below …

Know this bouncy overscrolling behaviour that browsers have been doing whenever you reach the “edge” of the page its contents?

elastic-scrolling
Bram.us, with bounce scroll

Sometimes – in fullscreen apps for example – you’ll want to disable this. Now, there’s no need to resort to JavaScript and hijack touchstart, as the little CSS snippet below can prevent the rubber band scrolling:

html,
body {
  position: fixed;
  overflow: hidden;
}

Tested with iOS8, iOS9, and iOS10.

However, as this snippet disables *all* scrolling on the body, if you want to retain scrolling on your page (but now without the overscroll effect) you’ll want to make use of a scrollable wrapper that spans the entire window/screen and which wraps around your entire content. Like so:

<html>
…
<body>
  <div class="mainwrapper">
    …
  </div>
</body>
</html>
body > .mainwrapper {
  width: 100vw;
  height: 100vh;
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch; /* enables “momentum” (smooth) scrolling */
}

You’ll most likely want to remove the margin and padding from the body too in that case 😉

Note that your mileage may vary. Other pure CSS solutions do exist (untested though)

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.