Wednesday, November 9, 2011

Hide the URL Bar in Mobile Safari - Fast!

Damn you, 60 pixel URL bar.
As a web developer who's recently made the transition to working more on mobile, one of the APIs that I *really* wish mobile browser vendors would provide is one to hide the URL bar. At the moment I'm most concerned with our iOS users and reclaiming that 60 pixels at the top:



There are a lot of exhaustive and good posts about the hack to hide the navigation bar - using window.scrollTo(0, 0).

One thing that kept tripping me up in my own app was the fact that it's an absolutely positioned, full-screen web app - in other words, content did not naturally flow past the bottom of the viewport, so scrollTo wouldn't hide the bar unless I made the app the size of the screen minus the url bar and I didn't really want to set the body height in script after loading like some of the other examples I saw and which I found to be pretty brittle - client.innerHeight eat your heart out.

The other hassle I found was that event though I could get text on the screen and was deferring all scripts to the bottom of the page, there was no way to get the stupid bar to hide until the arbitrary body onload event finally hit.

Make BODY onload fire ASAP.
Ultimately my fix involved removing all the SCRIPT or IMG tags from the DOM initially, in other words anything that might prevent body onload from firing immediately. Now there's a single body onload listener which fires super fast and hides the navigation bar and *then* it uses LABjs to load/run any scripts. The following bit of CSS + SCRIPT ensures that the navigation bar goes away like lightning:

CSS:

/* Portrait mode */
@media screen and (max-width: 320px) {
  .container {
    min-height: 416px;  /* viewport 356px + 60px navigation bar */
  }
}
/* Landscape mode */
@media screen and (min-width: 321px) {
  .container {
    min-height: 268;  /* viewport 208px + 60px navigation bar */
  }
}
SCRIPT:
body.onload = function() {
  window.setTimeout(function() {
    window.scrollTo(0, 0);
  }, 0);
  window.setTimeout(function() {
    $LAB.setGlobalDefaults({
      'AlwaysPreserveOrder': true
    });
    $LAB.runQueue();
    {% if is_production %}
      $LAB.script('https://ssl.google-analytics.com/ga.js');
    {% endif %}
  }, 0);
}; 

My house, my rules, I win.
There are lots of good reasons to use a script loader if you have multiple scripts, but for my production app I have only one. Maybe the 5k hit of LABjs is unnecessary in production, but this seems an acceptable tradeoff at the moment - I really like the queueScript and queueWait API in LABjs.

Fortunately the loading spinner image in our CSS file doesn't hold up body onload, so the overall net effect is really lovely - URL bar gone, spinner spinning, code loading then running - ideal. This approach seems to get rid of the URL bar faster than any other app I've seen which does so (gmail, Financial Times, Twitter).