Posts Tagged ‘media queries’

Dealing with device orientation (Mobile web part 6)

Tuesday, 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!

In this 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

Related

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

Using mobile-specific HTML, CSS, and JavaScript (Mobile web part 5)

Tuesday, June 29th, 2010

Mobile-specific HTML

Use the viewport tag to properly fit the content to the screen:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>

Use the tel scheme for telephone numbers, allowing users to initiate calls by clicking:

<a href="tel:18005555555">Call us at 1-800-555-5555</a>

Or use the sms scheme to initiate an SMS message:

<a href="sms:18005555555">
<a href="sms:18005555555,18005555556">          <!-- multiple recipients -->
<a href="sms:18005555555?body=Text+goes+here">  <!-- predefined message body -->

You also have access to several Apple-specific tags to use in your web application (iPhone and iPod Touch):

// iOS 1.1.3+: this is the icon that's used when the user adds your app to the home screen
<link rel="apple-touch-icon" href="/custom_icon.png"/>

// iOS 3+: full-screen startup splash screen image
<link rel="apple-touch-startup-image" href="/startup.png">

// enable full-screen mode
<meta name="apple-mobile-web-app-capable" content="yes" />

// controls the appearance of the status bar in full-screen mode
<meta name="apple-mobile-web-app-status-bar-style" content="black" />

And also some handy attributes to turn off annoying autocorrect features (works on iPhone, but anything else?):

<input autocorrect="off" autocomplete="off" autocapitalize="off">

Mobile-specific CSS

Media queries enable you to target specific features (screen width, orientation, resolution) within CSS itself. You can use them two ways: 1) inline in a CSS stylesheet or 2) as the “media” attribute in the link tag, which targets an external stylesheet. The following is an example of inline CSS that’s applied only when the device is in portrait mode:

@media all and (orientation: portrait) {
	body { }
	div { }
}

Here’s the same media query using the other method, which points to an external stylesheet (not recommended, as it creates another network request):

<link rel="stylesheet" media="all and (orientation: portrait)" href="portrait.css" />

Here’s a few examples:

// target mobile devices
@media only screen and (max-device-width: 480px) {
	body { max-width: 100%; }
}

// recent Webkit-specific media query to target the iPhone 4's high-resolution Retina display
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
	// CSS goes here
}

// should technically achieve a similar result to the above query,
// targeting based on screen resolution (the iPhone 4 has 326 ppi/dpi)
@media only screen and (min-resolution: 300dpi) {
	// CSS goes here
}

Read more: Media queries (Mozilla Developer Center)

(edit: removed section on body[orient='portrait'], which requires JavaScript and is in error. See Part 6: Dealing with device orientation for more details.)

Mobile-specific JavaScript

window.devicePixelRatio: determine screen resolution (analogue to the media query). (iPhone 4 has the value 2, while Nexus One has the value 1.5).

window.navigator.onLine (boolean): not strictly mobile, but helpful for apps to determine if they’re being run offline

window.navigator.standalone (boolean, iOS 2.1+): determine if it’s running in full-screen mode (Apple only?)

touch events (iOS, Android 2.2+): touchstart, touchmove, touchend, touchcancel

gesture events (Apple only, iOS 2+): gesturestart, gesturechange, gesturend give access to predefined gestures (rotation, scale, position)

window.orientation and orientationchange event: triggered every 90 degrees of rotation (portrait and landscape modes)

MozOrientation (Fennec/Firefox Mobile, Firefox 3.5+): also not strictly mobile. Gives access to the device’s accelerometer (x-y-z orientation data), updated periodically. Works on Android phones (I tested on the Nexus One), Windows Mobile, etc. On the desktop this works with laptops such as Thinkpads and MacBooks.

-near future: camera/microphone access in Android 2.3 (announced at Google I/O 2010) through the Capture API

If you’re developing for a BlackBerry Widget, you have access to proprietary information through the blackberry object (which gives access to useful information such as blackberry.network [returns values such as CDMA and Wi-Fi] and blackberry.system).

You also have the option to use PhoneGap, which augments JavaScript and gives you access to more phone features that native apps would have access to.

Use a mobile-optimized JavaScript library

Because smartphone browsers are standards-based, the aim of a JavaScript library on mobile is less towards API normalization and more towards providing an actual UI framework, usually to emulate the feel of native apps (and to provide easier workarounds to lack of access to position:fixed). We’ve seen a few libraries released that emulate the iPhone UI, and in the future we might see libraries emulating the Android UI, as well as entirely new UIs.

There’s also a bit to be said about simply loading full desktop JavaScript libraries into mobile clients. In my opinion this doesn’t particularly make sense, especially in a world where latency and bandwidth are so much more of a concern. It doesn’t make sense to force the user wait longer and download code that’s ultimately useless to them (hacks for desktop browsers such as IE 6, etc).

Here’s a few of the mobile libraries now available:
-Sencha Touch
-baseJS
-XUI
-jQTouch: jQuery-style mobile library that emulates the look of the iPhone UI
-iUI: the first JavaScript library to be released, emulates the look of the iPhone UI. Originally coded by Joe Hewitt.
-PastryKit: Apple proprietary non-public JavaScript library with no available documentation

Take advantage of new stuff!

While not specific to mobile, there’s a lot of new stuff in general that you can use. If you limit yourself to the top smartphones (iPhone, Android, and maybe webOS), compared to the desktop you immediately have access to an even wider array of new stuff, especially many Webkit proprietary features, since most of these top smartphones have browsers based on Webkit.

-HTML: new tags (HTML5 (I’m sure you’ve heard of it by now…))
-CSS: 2d transforms, 3d transforms, animation, border radius, custom fonts with @font-face, etc.
-JavaScript: strict mode, constants, block scope, Date.now(), etc.

Related posts

This post is part of a series on the mobile web. You can read the other parts of the series here:
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