Fix Your Position: Even In iOS 4

Recently, I've been given a few designs requiring a nav fixed to the top of the browser. In a perfect world, I could set position:fixed on the nav, sit back, and bask in the glory of CSS simplicity. Unfortunately, in the real world it isn't that easy. Surprisingly, the browser I've had the most difficulty with is Mobile Safari prior to version 5.

I've found multiple solutions to this problem; however, none of them really seem to work. These solutions are typically testing the element for position:static behavior, which is common in older browsers (I'm looking at you IE). Older versions of Mobile Safari, however, incorrectly report no support for position:fixed in Mobile Safari 5.

I was really hoping to build an extension to Modernizr (more on that later), so I went to the Modernizr docs and found a promising example:

  
Modernizr.addTest('positionfixed', function () {
  var test  = document.createElement('div'),
      control = test.cloneNode(false),
      fake = false,
      root = document.body || (function () {
        fake = true;
        return document.documentElement.appendChild(document.createElement('body'));
      }());

  var oldCssText = root.style.cssText;
  root.style.cssText = 'padding:0;margin:0';
  test.style.cssText = 'position:fixed;top:42px';
  root.appendChild(test);
  root.appendChild(control);
  
  var ret = test.offsetTop !== control.offsetTop;
  
  root.removeChild(test);
  root.removeChild(control);
  root.style.cssText = oldCssText;
  
  if (fake) {
     document.documentElement.removeChild(root);
  }
  
  return ret;
});
  

This works great on the desktop since most current browsers support position:fixed, but it fails on the iPhone making it useless for my purposes. After playing with a few variations of this, I decided to set it aside for another day.

Now, here is my solution. I make no claims that it is elegant, clever, or genius. But it works.

To add it to your page, simply looks like this:

  
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.js"></script>
<script src="js/jquery.ba-throttle-debounce.min.js"></script>
<script src="js/jquery.fix.js"></script>
<script src="hasPositionFixed/js/jquery.hasFixed.js"></script>
<script>
  $.hasPositionFixed(function() {
    $('nav').fix();
  });
</script>
  

The initial call to $.hasPositionFixed creates a hidden iframe on the page that loads in iframe-content.html. The JavaScript in iframe-content.html creates a fixed position element, scrolls the iframe, and checks the position of that element to see if it's correct for a fixed position element. It then sets 'hasPositionFixed' in the parent window based on the result of the scroll test.

Because it takes some time, load the iframe and do the testing. The test becomes asynchronous at this point, as $.hasPositionFixed accepts a callback to be run once support has been determined. With this method, things such as initializing a sticky nav bar can be done in an intelligent manner.

Like I said, this is a somewhat brute force attempt to detect support. I looked for an elegant solution, but so far it has evaded me. If you have thoughts or suggestions, fork the repo to see what you can do, drop me a line on twitter, or send me an email. I would love to see what solutions the larger JavaScript community comes up with.

Head over to Github to check out the files for yourself.

Keep an eye out for Part 2 on this topic. I'll cover the step after learning whether or not position:fixed is supported.