Media Queries for Android

Media Query and You Shall Receive

There are plenty of resources online talking about using “media queries” in your CSS to deliver sites optimized for the iPhone or iPad. But what about Android? Not a whole lot that I could find.

I spent most of a Saturday methodically checking a simple web page in a desktop browser, the iOS Simulator, and the Android Emulator using a variety of media query entries in my stylesheet.

Android is much more difficult owing to the many different screen sizes, resolution, and LCD densities – not to mention users who have “rooted” their Android device can change the LCD density to whatever they like. On top of that there are inconsistencies between Android versions and many devices give false pixel sizes too (the iPhone and iPad are guilty of this as well). This makes for a ton of stuff to “query” in your CSS in order to deliver optimized content.

For starters, any version of Android prior to 2.0 won’t understand the “orientation” media query (no version of iOS prior to 3.2 will understand it either). Secondly, Android 2.2 (and later, so far as I could tell) don’t process media queries correctly if 1) you are using device widths and 2) if you haven’t put a meta tag for “viewport” in the head of your page. Whether you want end users to be able to still tap-to-zoom a page is up to you, but I’m going to focus on delivering content designed to fit the user’s screen. I’m not going to waste my time on supporting outdated devices, but instead focus on the current and next generation.

Designing for “mobile” used to exclusively mean “phones.” That’s no longer true with the advent of the iPod Touch, iPad, and a plethora of other tablet devices running Android, WebOS, Blackberry OS, Windows Phone 7, and we may see other mobile touch-enabled devices running operating systems and browsers we haven’t even heard of yet. The screen sizes on all these devices can vary greatly – from a QVGA screen at 240 x 320 pixels an 120 dpi density to a WVGA 800 x 1024 screen at 320 dpi or even full HD quality screens. Then, of course, there’s the iPhone 4 with its “retina” display that has a 360 dpi density and there are even higher resolution mobile screens in the works!

Android and Orientation

As noted above, Android versions prior to 2.0 won’t understand the “orientation” part of your media queries and iOS prior to 3.2 won’t either. You can still include them, of course, for any newer mobile OS that does understand them, but if you’ve constructed your stylesheet well it should be clear which are intended to be “portrait” or “landscape” styles.

That’s great for things with an appearance totally controlled by the stylesheet, but what about elements adjusted by javascript? Well, you have two options: onresize and onorientationchange. You can either do these unobtrusively with even listeners or inline in the body tag of your page(s). The “onresize” is going to be better supported, but I’ve run into instances where a function checks if Height is less than or greater than Width to determine if it is in portrait or landscape orientation and the “onresize” was not correctly adjusting things, but “onorientationchange” (firing the SAME function mind you) did what it was supposed to do!

But there is an annoying (VERY annoying) problem with “onorientationchange” on Android devices. Namely that it can end up firing in an infinite loop! For some bizarre reason if you have an alert box (or any other modal dialog) it causes the browser to think an orientation change has occurred. If you orientation change also fires a dialog of any kind you end up stuck in a never-ending loop of (non-existing) orientation changes!

Thankfully there is an easy fix. You simply need to check if the orientation ACTUALLY changed before doing anything else (and especially before triggering any modal dialogs). Just do this:

onorientationchange=”if(window.orientation){…whatever it does…};”

Which Width?

You have two choices in your media queries – “device-width” and “width.” The former is a bit better supported, but if you want your stylesheet to also deliver optimized content to desktop users you’d need to use the “width” settings. If you’ve already set a meta “viewport” tag defining the width as the
“device-width” (in theory) devices should be able to correctly understand the “min-width” and “max-width” to mean the same thing as “min-device-width” and “max-device-width” but it may still not deliver the results you were expecting. But we can’t go without the meta viewport tag because mobile devices may zoom or scroll in ways you didn’t intend. Android requires the meta viewport tag for mobile optimized content anyway so we will have to use it, but does not require setting “width=device-width” (more on that at the end of this post).

What you need to know is that devices lie about the screen size. Apple’s iOS devices will tell you the pixel device-width is the same whether you hold them in portrait or landscape (always using the lesser of the two device-width sizes) because they don’t re-scale pages on orientation changes. Android isn’t much better in that it might tell you the width in landscape is 800 pixels, but the actual visible width is something less (sometimes significantly less). Plus those dimensions will change based on the LCD density setting, which may or may not reflect the actual density of the screen and has nothing to do with the reported “device-width” size. Consider that an iPhone running iOS 4.0 will say the landscape device width is 320 pixels (same as portrait) and the page width is 320 pixels, but iOS 4.1 will still say the device with is 320 pixels but the actual width is 480 pixels. Do you use a “max-width” of 320 or 480 pixels? You’d think 320 since that’s what the iPhone seems to prefer saying it is, but in practice you target 480 (the actual screen width in landscape).

So what are we supposed to do if we can’t get good numbers? Well, the only thing we’re concerned about is whatever the device says the available width is. In some cases this will still mean horizontal scrolling (because devices lie) but the user of the device is probably accustomed to that behavior so we probably shouldn’t be trying to work around it. I picked “min-width” and “max-width” because I really only care what the pixel width supposedly is and I could care less what the “device-width” might be. In some cases I’d also want to know the pixel aspect ratio as well, because that will tell me if I should be swapping out lower or higher resolution images.

The Meta “Viewport” Tag

Typically you will see tutorials on designing for mobile devices tell you to include a meta viewport tag along the lines of the following:

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

However, on an iPad that has now tied your media-query height and width to what the device says it is (and as mentioned the iPad lies and claims it is 768 pixels in either orientation). This will break a “min-width” or “min-device-width” media query condition such as this:

@media only screen (min-width: 800px) and (max-width: 1024px) and (orientation: landscape) {}

You might think that would target a layout for a screen wider than portrait mode (768 pixels) and up to the landscape width (1024 pixels) but the “device-width” viewport setting will think the iPad is only 768 pixels wide no matter how you hold it!

Which is why I advocate simply targeting the available width reported by the web browser – which is consistent for both the media queries and javascript. My meta viewport tag looks like this:

1
<meta name="viewport" content="initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />

Notice how it prevents scaling of the layout but also doesn’t link my media-query values to the reported (and possibly incorrect) “device-width?” Of course this also means I can’t use “device-width” or “min-device-width/max-device-width” in my stylesheet. Instead I need to use straight pixel queries like “min-width/max-width” even though they are not as widely supported you’ll only be freezing out older devices and browsers (and of course Internet Explorer versions prior to 9).

Note: if you don’t believe me that this is preferable, replace the meta tag in my media-query test document with the first example above and note how it breaks in the iPad but automatically alters the layout perfectly with the second meta tag.

Start Big and Dumb and Go Smart and Small

Older browsers aren’t going to know what the media queries are. You have two options for that:

  1. External linked stylesheets with a default one that has no media queries and the tag doesn’t have any such conditional arguments either. The old browsers will ignore the other stylesheets and, even if they load, ignore all the media queries within them.
  2. Put all your styles in one stylesheet and let the chips fall where they may.

Step one is to design your fallback “default” styles that will be served up to older browsers. A good rule-of-thumb is to design this for Internet Explorer, at least back to version 6 (which is still in use throughout much of the business world).

Every step after that is either adding bells and whistles for modern browsers and/or optimizations for mobile devices. Each media query that tests “true” will overwrite the default values, but it’s possible for a given device to test positive for more than one condition at the same time, in which case it will use the styles from the last true condition (hence the reason the stylesheets are called “cascading”). That means we need a tight structure on our stylesheet media queries that provides a condition for every conceivable real-world possibility. Any gaps in the arguments will fall back to the default values.

It’s also a good idea to cascade your media queries from the largest size to the smallest. To understand why you need to understand what “min-width” and “max-width” mean in a media query:

  1. max-width <= value
  2. min-width >= value

Notice how that says greater or less than AND equal to? Even though you don’t use those operators if you wrote (max-width: 1024px) it would be processed as “less than or equal to 1024 pixels.”

Most of the media query examples I’ve seen use only one condition such as (max-device-width: 480px) and maybe there’s another query that says (min-width: 800px). The first is usually intended for an iPhone, the second would be for desktop or laptop system and maybe an iPad in landscape orientation. But what happens if you have an iPad in portrait orientation or an Android device with a 640 pixel width? They fall through the cracks and get the default fallback styling. Also, notice that the iPad will tell you the device width is 768 pixels in both portrait or landscape, but in landscape the actual width will be 1024 pixels.

When you create your media queries make sure conditions don’t step on each other’s shoes unless that’s what you intended them to do. It is possible for a device to test positive for more than one media query at a time and it will apply the last true condition (“cascading” remember?). For example, if you had two media queries with one (max-width: 640px) and another later on that says (min-width: 640px) those are respectively interpreted as “less than or equal to 640 pixels” and “greater than or equal to 640 pixels.” So what happens if the width is equal to 640 pixels? Only the last styles get applied, which may not be what you want because it could be applying styles intended for widths larger than 640 pixels to a screen that is only 640 pixels wide. To avoid this tighten up any gaps in your stylesheet by making sure the minimum sizes are one pixel different than the next media query maximum, and cascade styles from largest to smallest, plus I prefer landscape to portrait (since your largest size will be for big widescreen monitors used on desktop systems you’ll already be starting with “landscape” orientations).

A last point on assembling your bulletproof mobile-ready stylesheet would be to make your layouts as “fluid” as possible! Then you can use percentages for dimensions and set your fonts in “ems” and you may not need many (or any) media queries at all. However, in the real world a perfect fluid layout is often difficult to pull off for all but the most simple of sites. Plus, if you know you’ll be scaling images you can use media queries to actually swap them out with optimized ones – so your widescreen desktop viewers with an HD monitor get the high-res images and your QVGA Android device visitors maybe don’t load any images at all. A final word of warning on fluid layouts is that, since devices lie about dimensions, 100% can sometimes be more than the viewable area on the screen. You’ll want to do a lot of testing to decide whether to code a width at a percentage or a pixel size.

Note: the stylesheet for my web site’s “splash page” and for the custom WordPress theme I’m using for this blog don’t use ALL of the following queries. The WordPress theme only uses TWO queries – one for iPad in portrait mode and another for smaller screens (encompassing iPhone, iPod Touch, Android phones and tablets regardless of orientation). In some cases you’ll only need to use a couple of them to adjust a few elements of your layout if it is sufficiently fluid to begin with.

So my cascade ended up being:

Default/Fallback Styles
Widescreen Monitor
Desktop/Laptop Screen
Landscape
iPad Landscape
Android WXGA and WVGA854 Screens
Android HVGA and WVGA Screens
Android Small Screen
iPhone + iPod Touch
iPhone 4 Retina
Android QVGA
Portrait
iPad
iPhone+iPod+Android Phone
iPhone 4 Retina + Android XDPI
Android “Tablet” @ default density
Android Phone @ default density
Android @ High Density

Since Android device manufacturers can set the LCD Density, as can users who have root access to their device, it’s worthwhile to look at what “density” means on Android.

Android is Dense

The LCD Density setting on Android is something like the screen resolution on your desktop computer, except it scales the UI up or down. The numbers seem a little counter-intuitive at first because we’re used to how the icons and text look smaller on a desktop computer’s screen as you increase the resolution value, but on Android as you increase the density value the icons and text look larger. This deserves a quick explanation!

Android uses something called a “device independent pixel” or “dip” that assumes 1 dip = 1 screen pixel @ 160 dpi (the default density) using the formula dips * (density/160). If you increase the density to, say, 240 dpi then it scales the dip units up to equal 1.5 screen pixels – or in other words it blows things up by one and a half times. If you lower the density to 120 then it would scale things down to 75% their normal size. This is why things look smaller as you lower the density value and look larger as you increase it. Incidentally the iPhone “Retina” display does something similar, however Apple can more easily control that platform and chose a display that simply doubles the pixels. Not so counter-intuitive anymore is it? Onward!

Android ships with four standard densities:
120 dpi = ldpi (Low Density)
160 dpi = mdpi (Medium Density)
240 dpi = hdpi (High Density)
320 dpi = xdpi (Extra High Density)

They offer a method, in the meta “viewport” tag, to declare the density either as a specific value (probably a bad idea) or as one of the named standard densities or as whatever the setting on the device is (probably the best option). The format for this is:

 

There doesn’t appear to be a corresponding version for CSS media queries, but we can follow Apple’s lead and just use Pixel Aspect Ratio settings instead! For Apple’s “Retina” display we know it scales things up 2 times which is why we can target it in our media query with:

(-webkit-min-device-pixel-ratio: 2.0), only screen and (min-device-pixel-ratio: 2.0)

Here’s the table for doing the same with Android comparing Pixel Aspect Ratios to dpi settings:
0.75 = 120 dpi = ldpi = Low Density
1.0 = 160 dpi = mdpi = Medium Density
1.5 = 240 dpi = hdpi = High Density
2.0 = 320 dpi = xdpi = Extra High Density (notice it’s the same as “Retina” for iPhone)

Which Query Gets Applied?

I went through and checked how setting different densities on Android affects which media query styles get applied. What I discovered is that Android 3.0 “Honeycomb” behaves in a different way from the 2.x versions! Here are the results of my testing from 100 – 240 dpi at 10 dpi intervals:
Donut 2.0 / Eclair 2.1 / Froyo 2.2 / Gingerbread 2.3
100 – 130 dpi: Desktop (landscape), iPad (portrait)
140 – 190 dpi: iPad (landscape), Android hdpi (portrait) [reports 640px width]
200 – 240 dpi: Android Small Screen (landscape), iPhone+iPod+Android Phone (portrait)

I assume 320+ dpi would simply use the iPhone 4 “Retina” / Android XDPI styles.

Honeycomb 3.0
100-190 dpi: Desktop (landscape), Android Tablet (portrait)
200-240 dpi: iPad (landscape), iPad (portrait)

At 200 dpi Honeycomb switches to a “Phone” UI similar to Gingerbread instead of “Tablet” mode.

So what screens and densities should you be worrying about when you make your stylesheet? Android supports a bunch of different screen types and sizes, some of which I accommodate specifically with my media queries:

QVGA = 240×320
WQVGA400 = 240×400
WQVGA432 = 240×432
HVGA = 320×480
WVGA800 = 480×800
WVGA854 = 480×854
WXGA = 1280×768

Notes:
QVGA & WQVGA will report as 320 pixels wide in portrait orientation even though they are actually 240 pixels wide because the 120 dpi density is scaling the “device independent pixels” of the UI down to 75% [320 dip * (120dpi/160dpi)=240 screen pixels]. You’d be unlikely to see any density other than 120 for these because the UI elements get scaled up so large they are unusable.

HVGA @ 160 dpi and WVGA @ 240 dpi are currently the most widely used screen sizes and densities for Android devices according to Google.

Remember that density settings / pixel ratios can affect the reported size on the device. You cannot go by the actual pixel size of the screen! This is where targeting specific pixel aspect ratios could help you optimize for the end user’s experience based on how zoomed in or out the UI on their device is. Or you can just target pixel widths and not bother yourself with whatever the scaling factor of the device happens to be.

Media Query Tester

See how it all comes together! To figure all this out I put together a simple Media Query Tester. All you need to do is click the link and, on a desktop system resize your browser window, on iOS change the device orientation, and on Android change the device orientation and the zoom setting. If the browser supports media queries you’ll see it dynamically applying the different colors defined media queries and the script will write the pixel dimensions and orientation of the window into the input field.

Please note that this test page will only work in modern web browsers that understand media queries. Older browser will only use the “fallback” styles defined before all the media queries. If you want to see “App View” sizes on an iOS device the page is coded so if you “Add to Homescreen” it will display full-screen like a native app.

← Previous post

Next post →

1 Comment

  1. kmhcreative

    After putting my media queries into action I can say that the the “orientation” option is fairly superfluous. Realistically all you care about is the screen width of the device and since the width is different in landscape or portrait you don’t also need to add (orientation: landscape) or (orientation: portrait) to the media query.

Leave a Reply


Menu




Search

Posts

About

Comment1

Contact

Share

Share this On:

Google+ Reddit Stumble It Digg this! LinkedIn Pinterest RSS Feed E-mail Link!