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:
<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:
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:
$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:
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.
The author does not allow comments to this entry
Comments
Display comments as Linear | Threaded