Dealing with device orientation (Mobile web part 6)

August 24th, 2010

Introduction

If you read part 4 of this series, you’ll know that because of the variety of screen sizes, the best way to develop for mobile is to develop fluid layouts that take 100% of the available space on the screen.

What you probably didn’t think of is that there’s different screen widths even on the same device! This is due to screen orientation. And when the user changes the screen orientation, stuff may break (hey, it’s not a perfect world). In my experience this has meant needing to tweak percentage widths on elements, but I imagine there’s even more needs. Imagine an image carousel that can only fit three images across in portrait mode but can possibly fit four images in landscape mode. It might be nice to re-initialize the carousel to accommodate a forth image when the phone is in landscape mode (then again, that might create a lot of unnecessary overhead…).

In any case, I hope you can agree that it would sometimes be useful to know the screen orientation.

window.orientation and the orientationchange event

Luckily on the latest smartphones you have some goodies available to you that you don’t have on the desktop (since desktop users aren’t in the habit of constantly turning their screens sideways!).

window.orientation: this property gives the current screen orientation: 0 in portrait mode, 90 when rotated left, -90 when rotated right (no special value when the screen is upside-down)

orientationchange event: this window event fires after every 90 degrees of rotation and, like other events, can be applied in various ways:

// DOM Level 0 (avoid)
window.onorientationchange = function(){};

// DOM Level 2
window.addEventListener('orientationchange', function(){}, false);

Some websites recommend using orientationchange to dynamically add an orient attribute on the body element and target the orientation with CSS selectors (body[orient=landscape]), but this is in error. As it turns out, orientationchange is only fired AFTER the screen has been rotated (which also triggers a CSS reflow), which means this attribute is updated later (after the reflow). And unfortunately editing this orient attribute doesn’t trigger another CSS reflow. The result? When you rotate the device, these new CSS styles don’t get applied!

The fix is to add the orientation as a CSS class, which does trigger a CSS reflow. So our code at this point will look something like this:

(function(){
var init = function() {
  var updateOrientation = function() {
    var orientation = window.orientation;
    
    switch(orientation) {
      case 90: case -90:
        orientation = 'landscape';
      break;
      default:
        orientation = 'portrait';
    }
    
    // set the class on the HTML element (i.e. )
    document.body.parentNode.setAttribute('class', orientation);
  };
  
  // event triggered every 90 degrees of rotation
  window.addEventListener('orientationchange', updateOrientation, false);
  
  // initialize the orientation
  updateOrientation();
}

window.addEventListener('DOMContentLoaded', init, false);

})();

Now we can target elements like this in CSS:

.portrait body div { width: 10%; }
.landscape body div { width: 15%; }

With a little help from media queries

You may have heard of media queries being used to target mobile devices (based on screen pixel width) or to target the iPhone 4′s Retina display, but you may not have known that you can also target screen orientation!

@media all and (orientation: portrait) {
  body div { width: 10%; }
}

@media all and (orientation: landscape) {
  body div { width: 15%; }
}

The orientation media query is available on iOS 3.2+, Android 2.0+, and some other browsers.

This is a lot cleaner than the above JavaScript example in the sense that it’s pure CSS, and it’s part of the CSS that gets reflowed when the screen is rotated.

(Minor note: iOS 4 on the iPhone Simulator running 4.0.0 looks like it’s stuck in landscape orientation, but the media queries work correctly on my 3GS with 4.0.1)

Fallback: when window.orientation and media queries aren’t available…

If window.orientation isn’t available on a device, chances are the orientationchange event and media queries (for orientation) will also not be available. Oh no, what do we do now?

Even though this isn’t an entirely foolproof method, we can dynamically measure the window width and height and guess orientation based on that:

(function(){
var HTMLNode = document.body.parentNode;
var updateOrientation = function() {  
  // landscape when width is biggest, otherwise portrait
  var orientation = (window.innerWidth > window.innerHeight) ? 'landscape': 'portrait';
  
  // set the class on the HTML element (i.e. )
  HTMLNode.setAttribute('class', orientation);
}
var init = function() {
  // initialize the orientation
  updateOrientation();
  
  // update every 5 seconds
  setInterval(updateOrientation, 5000);
}
window.addEventListener('DOMContentLoaded', init, false);
})();

Ok, so it’s not pretty, but it seems to work. The overhead in this fallback example is the fact that we have to use a polling technique (in this case every 5 seconds [5000 milliseconds]) to check for changes in orientation.

Note: there’s also the strong possibility that these browsers will not support the DOMContentLoaded event, but we’ll ignore that for the purposes of this article. (if you have problems, change DOMContentLoaded to load)

Putting it all together

Ok, so if you want the fallback example to work in addition to newer methods, unless you want to duplicate your CSS, then avoid using media queries to target orientation. Instead we’ll rely on adding a class to the html tag (or the body tag if you prefer).

Once we put everything together, we get something that looks like this:

(function(){
var supportsOrientation = (typeof window.orientation == 'number' && typeof window.onorientationchange == 'object');
var HTMLNode = document.body.parentNode;
var updateOrientation = function() {
  // rewrite the function depending on what's supported
  if(supportsOrientation) {
    updateOrientation = function() {
      var orientation = window.orientation;
    
      switch(orientation) {
        case 90: case -90:
          orientation = 'landscape';
        break;
        default:
          orientation = 'portrait';
      }
      
      // set the class on the HTML element (i.e. )
      HTMLNode.setAttribute('class', orientation);
    }
  } else {
    updateOrientation = function() {
      // landscape when width is biggest, otherwise portrait
      var orientation = (window.innerWidth > window.innerHeight) ? 'landscape': 'portrait';

      // set the class on the HTML element (i.e. )
      HTMLNode.setAttribute('class', orientation);
    }
  }
  
  updateOrientation();
}
var init = function() {
  // initialize the orientation
  updateOrientation();
  
  if(supportsOrientation) {
    window.addEventListener('orientationchange', updateOrientation, false);
  } else {
    // fallback: update every 5 seconds
    setInterval(updateOrientation, 5000);
  }

}
window.addEventListener('DOMContentLoaded', init, false);
})();

Minified (540 bytes):

(function(){var e=typeof window.orientation=="number"&&typeof window.onorientationchange=="object",f=document.body.parentNode;function c(){c=e?function(){var d=window.orientation;switch(d){case 90:case -90:d="landscape";break;default:d="portrait"}f.setAttribute("class",d)}:function(){f.setAttribute("class",window.innerWidth>window.innerHeight?"landscape":"portrait")};c()}window.addEventListener("DOMContentLoaded",function(){c();e?window.addEventListener("orientationchange",c,false):setInterval(c,5E3)},false)})();

Conclusion

And that’s it! Now we can reliably target different screen orientations with some straightforward CSS:

.portrait body div { width: 10%; }
.landscape body div { width: 15%; }

Again, in my experience I’ve used this to fix bugs. But I’m sure you can find more creative uses for it!

Related

iPad web development tips (Nicholas C. Zakas)
iPhone window.onorientationchange Code (Ajaxian)
The orientation media query (Quirksmode)

More from the Mobile Web series:

Part 1: The viewport metatag
Part 2: The mobile developer’s toolkit
Part 3: Designing buttons that don’t suck
Part 4: On designing a mobile webpage
Part 5: Using mobile-specific HTML, CSS, and JavaScript
Part 6: Dealing with device orientation
Part 7: Mobile JavaScript libraries and frameworks

Tags: ,

  1. Hemant Chanchlani says:

    Hi .

    I am working on a mobile dev website . Ran into some issues for orientation. I tried using your example but faced some errors . Can u please mail me an html with a running example.

    Would really appreciate your help.

    Thank You.

  2. [...] Dealing with device orientation Mobile web part 6 | David B. Calhoun – Developer Blog. [...]

  3. [...] Part 1: The viewport metatag Part 2: The mobile developer’s toolkit Part 3: Designing buttons that don’t suck Part 4: On designing a mobile webpage Part 5: Using mobile-specific HTML, CSS, and JavaScript Part 6: Dealing with device orientation [...]

  4. John says:

    This is Great code! I was looking for a solution to resize scrollable divs, and this integrated perfectly. Thanks for the code!

  5. thuy says:

    IE mentioned ‘Object Expected’ for this line:
    window.addEventListener(‘DOMContentLoaded’, init, false);
    })();

  6. thuy says:

    IE mentioned ‘Object Expected’ for this line:
    window.addEventListener(‘DOMContentLoaded’, init, false);
    })();

    How do I fix that…?

  7. essjay says:

    thuy – did you figure out your issue? Would love to know how you resolved this.

    Great blog, David. Much appreciated.

  8. SCSI says:

    All I can say is use JQuery, forget everything else!
    :)

  9. kl says:

    This is horrible! You don’t need DOMContentLoaded or load or anything like that!

    document.documentElement is a standard, cross-browser reference to root element (html) which is available all the time.

  10. kilian says:

    hi,

    thank you for your tutorial – it was really helpful for me. while looking for solutions for mobile websites i found Zepto.js which is “a minimalist JavaScript framework for mobile WebKit browsers, with a jQuery-compatible syntax”. look here:
    http://zeptojs.com/
    I think this could be very helpful, too.

    …and another remark to orientation: i think the window.onresize event should work, if no orientationchange is supported, what do you think?

  11. kilian says:

    oooh, maybe i should have read Part 7: Mobile JavaScript libraries and frameworks first…
    ;-)

  12. [...] Part 6: Dealing with device orientation [...]

  13. thugsb says:

    It’s a shame you’re using setAttribute to set the class on the html element. It would be much nicer if it added and removed those classes as appropriate. Otherwise it conflicts with Modernizr, which I don’t want to lose.

  14. David says:

    Just a quick note that this is already outdated. A resize event fires on screen orientation changes, which is a nice baseline. Just tie into that – none of the tricky code needed above.

  15. [...] Track Device Orientation. This library looks like it was developed in-house to detect whether a mobile device is displaying the page in vertical or horizontal aspect. A note says it is “Based very heavily on: http://davidbcalhoun.com/2010/dealing-with-device-orientation.” [...]

  16. Jeff says:

    Clear and Concise. ;-)

Leave a Reply