Skip to content

iOS7 - Mobile Safari And Viewport Units

The other day I decided it was time to start using viewport relative units - for example height: 100vh;. According to caniuse my all of "targeted browsers" should've worked fine. While The usual suspects - Android Stock Browser and Internet Explorer - behaved formidably, iOS 7 did not.


TL;DR: There's a "fix" for the viewport troubles on iOS: viewport-units-buggyfill.

If you haven't heard of viewport units, read CSS viewport units: vw, vh, vmin and vmax

Identifying The Problem

What's the first thing you do when you hit a WTF-moment in development? Exactly, you take a step back and build a minimal test case. You find the smallest amount of code that still reproduces your problem to nail things down. For this particular problem, my first test case looked like this:

<html>
<head>
  <meta charset="utf-8" />
  <title>viewport tester</title>
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
  <style>
    * { margin: 0; padding: 0; }
    #tester { height: 100vh; }
    #spacer { height: 3000px }
  </style>
</head>
<body>
  <div id="spacer"></div>
  <div id="tester"></div>
  <script>
    var windowHeight = window.innerHeight;
    var bodyHeight = document.body.offsetHeight;
    var tester = document.getElementById('tester');
    var testerHeight = tester.offsetHeight;
    console.log(windowHeight, bodyHeight, testerHeight);
  </script>
</body>

Much to my surprise the numbers were ok. The tester element actually had the height I expected it would have (exactly that of window.innerHeight). It took me a while to realize that I had been adding things to the DOM dynamically. So I extended the the test by a simple function:

setTimeout(function() {
  var el = document.createElement('div');
  el.style.height = '100vh';
  document.body.appendChild(el);
}, 1000);

Now the output looked more like what I was seeing in my app. 7303, 11606, 4303 On twitter I got pointed to iOS 6-7 handles viewport relative units really weird - a 7 month old issue describing what I was seeing on my test devices. According to that issue the problem already existed in iOS6, but my test devices disagreed - everything worked fine.

Working Around The Problem

You're probably familiar with the term deadline. I was on one. A colleague had already tried fixing this for 2 days and we had to present this thing the next day. A quick fix - a workaround - was all we were looking for at this point. We were in a lucky spot. Only two selectors were using the vh unit. The offending selectors only came into play when a certain view was rendered. Replacing the vh values with px values seemed crude, but simple enough to get the job done. Since the problem occurred on portable devices (tablets, phones), listening to orientationchange seemed like a good idea as well. The "solution" we shipped looks rather simple:

// WORKAROUND: converting 90vh to px
$element = $('.the-selector');
function fixMobileSafariViewport() {
  $element.css('height', window.innerHeight * 0.9);
}
// listen to portrait/landscape changes
window.addEventListener('orientationchange', fixMobileSafariViewport, true);
fixMobileSafariViewport();

Adding this jewel of a UserAgent switch into the mix and we had the perfect recipe for being fired on the spot:

var IS_MOBILE_SAFARI_7 = !!navigator.userAgent.match(/i(Pad|Phone|Pod).+(Version\/7\.\d+ Mobile)/i);

This is what we had committed to SVN (yeah, sorry, CorporateITā„¢). The widget we had been working on for two weeks finally alive and kicking on all our test devices. We went back to the hotel. The hotel's bar. To drown our sorrow. We knew what we just did. We added a non-scaling time bomb to our code base.

Solving The Problem

A snowball had a better chance surviving hell than above hack polluting my code for more than a couple of days. While the QA (Quality Assurance) engineers were satisfied (the tool was "working" after all), I looked into using CSSOM to make those vh to px calculations more generic. We were planning on using viewport units for more things in the future, so it made sense to sacrifice another workday for this. viewport-units-buggyfill is the thing now running in our apps. A simple viewportUnitsBuggyfill.init() and we can stop thinking about Mobile Safari's short comings.

The buggyfill doesn't work on style attributes. The adapted test still fails on iOS until you load the buggyfill.

Conclusion

Don't leave quick browser-bug fixes in your code base. Separate things. Try to make it as generic as possible to cover similar problems in the future.


The term "Buggyfill" was coined by Sebastian Golasch.

Comments

Display comments as Linear | Threaded

No comments

The author does not allow comments to this entry