Viewport relative unit strangeness in iOS 6.

Using the new viewport-relative units in CSS3 is compelling when creating flexible layouts across different screensizes. It is early days though, and it turns out there are some issues in browser implementations that are worse than others – and iOS 6 is a doozy in this regard.

Here’s the thing: setting certain height values in viewport relative units causes Mobile Safari on iOS 6 to dynamically recalculate and change the internal representation of viewport height as you interact with it, thus creating sort of an infinite loop.

Edit: Several people have asked me if I’ve found a fix for this. Sadly, I have not (but I must confess, I haven’t given it much time since discovering it), and the bug I filed with Apple is still open. Edit, again: still no fix for older iOS versions, but this bug is fixed in iOS 8.

Some background.

With percentages, we can style elements in relation to the size of their containing block, but sometimes we want to look at the viewport and size things in relation to that—one example could be typography, but there are others as well. This is acheived by using the viewport relative length (vh, vw, vmin and vmax) units from CSS3 Values and Units. Chris Coyier has written about some possible uses for typography over at CSS Tricks, and David Bushell has written excellently about tweaking stuff not only horizontally but vertically in responsive layouts. He encountered some weirdness in iOS 6 though, and I think I’ve run into the same thing. I haven’t been able to find much about these bugs elsewhere around the web so I thought I’d post my findings here, and see if someone else chips in with a better explanation.

Here’s my case: in a certain web site, I was playing with how to size elements on the page to completely cover the viewable area of the screen, and basically let the user either scroll through them vertically or jump through them via in-page links, sort of like a slide show. The first iteration of the idea came from playing with the responsive navigation pattern of jumping to the footer on smaller screens, and by letting the footer be at least as tall as the viewport to create the illusion of an overlay. Width isn’t too hard, so for now we’ll focus on making elements as tall as the viewport.

Percentages? Yeah but no.

This is doable with percentages up to a point, using height: 100% on the html and body elements, as well as on the element that is to cover the screen. The thing is that this will only work if the receiving element is a direct descendant of the body: any wrapping element will create a containing block with it’s own intrinsic height, and things fall apart quickly. If only there was a way to say "as tall as the viewport"...

There is a way.

Enter the height: 100vh-declaration. This works as expected in Firefox & Chrome from my tests so far, but when I try it in Mobile Safari on iOS, things get weird. If you have an iOS device at hand, you can play along at http://jsbin.com/ikediy/39/, I’ve also created an issue on Scott Jehl’s repository of Device Bugs, so there might be some discussion going on there.

Basically, the element that is set to display at 100% of the viewport height is rendered correctly at first, based on the initial viewport size. Remaining content on the page (either sized in viewport units or not) is then rendered, adding to the total height of the layout. The next time something on the page causes a any sort of layout recalc, the value of the viewport height is calculated based on the total layout size, and the element set to use vh-units is recalculated to that value instead, thus causing further expansion of the vertical viewport size. And round it goes: playing around with the page quickly gives you an element that is millions of pixels tall. Note: this seems to only happens when the viewport relative content + the rest of the page is greater than the height of the initial viewport.

Here’s a crappy image of what I think is going on:

Illustration of viewport relative measurement bug in iOS.

As mobile safari tries to do the layout of the page, each repaint seems to add to the total viewport size. The topmost, grayish layer is the visible viewport. Beneath that, the next reddish layer is what the browser sees as the viewport size internally. Next is the content: the viewport relative box (yellow) and some other content (brownish). From left to right, each successive layout recalculation causes the measurements to change.

Viewports make me dizzy

I know my language about viewports and rendering is vague in the preceding paragraphs. That’s because I have no idea of what goes on in the viewports-all-the-way-down innards of Mobile Safari. Man, viewports are hard, let’s go javascripting... But seriously, I think this is probably related to the Tale of Two Viewports that PPK writes about. Reading about the various models of viewports and measurements thereof in that article gave me a headache, but I’m guessing that’s where the issue lies: a viewport is not a viewport, except when it is. The behavior in iOS 6 seems extremely stupid to me though. Here’s to hoping it’s changed in iOS7.