Saturday, October 16, 2010

Practical CSS3 Media Queries and Mobile Browsers

tl;dr: Jump to the end of this post ↓

I've been working on-and-off on a little project on the side, and have been trying to make it as buzzword-compliant as possible, because, you know, that's what all the cool kids are doing. Making it buzzword compliant meant that I had to use persistant connections to the server for real-time data exchange, use non-blocking I/O, I just had to use node.js, and throw in generous portions of NoSQL. So I did all of that, and now my server is worthy of a new starburst sticker.

Now that the server was mostly ready, I shifted attention to the client. Clearly, I had to use HTML5 and CSS3, think heavily about Web Performance Optimization, and ensure that the usability trolls don't get a chance to make me look bad. That was the easy part, since I have been doing these things for years. The fun started when I decided to flirt with an idea I had never played with before.

The Mobile Web

Mobile devices, it seems, have finally arrived. We have forever dreamt of an always-connected world, and mobile devices seem to be delivering on that dream. Indeed, we are on the verge of having more traffic from mobile devices to the web than from desktop browsers[citation needed]. In such a scenario, it's impossible to ignore mobile devices.

The problem is that no one knows what the mobile web means. In such a case, it's absolutely fine to interpret it the way you want to, and that's what I did. So, here's what the mobile web means to me.

The mobile web is that part of the web that works well on mobile devices.

Pure genius of a definition, isn't it? I know. Thank you. There's an ambiguity in the definition there though — what does it mean to work well? Turns out, no one knows that either. But there are some hints:

  • It should work fine in the mobile browser — no native apps, no downloads.
  • Context is king. The site should provide the right amount of data that is needed in a mobile user's use-case at that time.
  • The site should be touch friendly, meaning take care about hit areas and stuff because some people have fat fingers. Not me — my fingers are beautiful. Just thought you'd like to know.
  • Lightweight and fast. Because I want my data quickly, and I'm paying by the byte. This is true of the desktop as well, but especially true of the mobile web, because network operators try to suck as much as possible out of the wallet by giving as little as possible.

So, how do we go about building such a site? Generally speaking, this necessitates building a separate version of the site just for mobile browsers. Trust me, this effort is worth it. Don't believe all those standardistas who say change your CSS file, and everything will be magically fine. Their latest mantra is use CSS3 media queries and everything will be magically fine. Like most other things in life, nothing is that simple.

But there are times when that actually works. And this post is about those times. This post is only useful if:

  • Your site is already light enough and tightly focussed enough that the markup for your desktop version is going to be the same as that of your mobile version.
  • There's not really too much contextual data you need to provide to your mobile users, so again the markup is the same.
  • Your site is styled primarily with CSS. You are doing that already, aren't you?
  • You are lazy, and don't want to create multiple versions of the same content, otherwise known as maintainability or code reuse.

I'm definitely lazy, and I'm proud of it. I'd rather not write any code at all. If I have to write any code whatsoever, I'd rather write it such that (a) I never have to look at it again, (b) I keep using the same thing over and over again so I don't write the same thing ever again, and (c) I should write as little as possible. Fortunately, these are all considered good practices — maintainability, reuse and conciseness. I bet whoever said these were good practices was lazy. But as long as the rest of the world doesn't find out, this can be our little secret.

There's a problem with being lazy though. You have to work hard to be lazy. How ironic is that! And hard work is, well, hard. So, there's where I was, wanting to be lazy, tinkering with the idea of the mobile web, and looking at CSS3 media queries from the corner of my eye. Being lazy, before I wrote a single line of code, I wanted to know everything about challenges for mobile phones and media queries. I'll cut a long story short, and tell you my findings.

  • There's no standard screen size for the mobile. If you plot a graph of mobile screen sizes, the graph is going to be all over the place. That basically means you have to design for all screen sizes! This is going to be fun. Not.
  • Media queries let you target styles to different ranges of screen sizes. Awesome. Seems like a right fit for the job. So, we can have a different CSS file targeted to small screens, a different ones for medium sized screens (like the iPad), and the full of crap ones for desktop browsers. Neat. Except...
  • CSS3 media queries don't work in most mobile browsers and IE! Bam! Just when we thought we had a solution. Well, to be fair, it does work in some mobile phones — the iPhone's and Android's WebKit browsers being good examples, but both these browsers put together are such a small percentage of mobile users, it's not even funny. Blackberry's horrid browser, Opera Mobile and Symbian's native browser have the largest market share of all mobile browsers. We can't just ignore them, can we! Just like we reluctantly show love to IE, we'll have to show some love to these browsers as well. It's that kind of a relationship. (BTW, I think Opera Mini does support CSS3 media queries. I haven't tested this.)

So, I was going to throw the idea of media queries completely out the door, you know, baby and bath water and all that. This is when I saw an excellent talk by Luke Wroblewski. What he is advocating is that we should build for mobile sites first, and for desktops later. His arguments are mostly non-technical and about usability, design and innovation, but he underscores the importance of designing for the mobile. I get that, but it doesn't help me be lazy. Well, it could, but I didn't know how. It still looked like double the effort. Except, he had planted an idea in my head. You know, much like Inception. Come to think of it, Luke does look a little bit like DiCaprio, doesn't he? Cool!

Mobile First

So far, I was thinking of making separate CSS files for mobile and desktop, and doing so in parallel. That mostly meant I'd do the desktop version first, and the mobile version next. Then I'd target different screen sizes with media queries (which I discovered wouldn't fly) and voila it would work everywhere. Instead, Luke's talk got me thinking: what if I design for the mobile first, and then target desktop browsers with media queries. This would theoretically work, since most mobile browsers are old, and they get the vanilla CSS files. For newer mobile browsers and most desktop browsers since they are new as well, I'd serve the full css file targeting them with media queries. Newer mobile browsers will understand it's not for them, so they'll ignore it. Older mobile browsers won't understand it at all, and hence ignore it. Most desktop browsers would know that the CSS file is for them, and hence apply it. This leaves our old nemesis Internet Explorer, but we're used to that already. We'll solve that problem later.

Now, about actually pulling it off. I found this document at Dev.Opera that goes into a fair amount of detail about media query parsing differences between browsers, old and new. This doc would definitely help. Now, I needed to figure out how to structure the CSS. Remember my laziness? This meant that there should be as little repetition between the mobile version and the desktop version. What would generally be the things that remain common across the two types of browsers? There's actually quite a bit. There's color schemes, there's some type information, and possibly some CSS niceties like rounded corners, gradients and effects. What's going to be different? It's mostly going to be font-sizes and layout. Sounds good so far, let's start implementing it.

The code

Since we've decided to work only with mobile browsers first, and serve it CSS like it's the 1990s, the following markup should do it.

<link rel="stylesheet" type="text/css" href="narrow.css" />

I actually went ahead with this and built the whole mobile version of the site, and tested it in Mobile Webkit on Android and iPhone and in Opera Mini, which are the two browsers I had access to on a couple of devices. I kept the layout fluid, since I didn't know the screen size before hand, and didn't want the site to break in screen sizes I didn't have control over. I strongly recommend that you go with fluid width layouts for narrow screens as well. I even tried it in a couple of emulators and it all looked fine. Of course, some of the fancy CSS rules didn't work on some browsers, but that was ok — things still looked reasonably pretty and it looked mobile-web-optimized. Awesome. Now, I turned my attention to the desktop. This was when the Dev.Opera doc came in handy. This is what my markup looked like:

<link rel="stylesheet" type="text/css" href="narrow.css" />
<link rel="stylesheet" type="text/css" media="all and (min-width: 400px)" href="wide.css" />

See that media attribute there that says media="all and (min-width: 400px)"? That's the media query. It means target this CSS to all media that have a min-width of 400px. Why all media? Because I was being lazy and didn't want to work on a print version at the moment. Also, it seemed like the most reliable media type to target from Opera's documentation. Why base this on min-width? Because that's the best way to target different device sizes. Why 400px? Because in portrait mode it would apply the narrow.css (screen widths are generally 320px for devices that support orientation), but in landscape mode or for most desktop browsers, it would apply the wide.css file. This suited me, since I wanted to utilize the extra width in landscape mode better. YMMV, and you should play with this number to suit your tastes.

Also, since I was applying both the files and hence cascading their rules, I could already go with most of the stuff defined in narrow.css. wide.css only had to override some properties — mostly font sizes, block positioning and layout. Tested this, and it continued to work with all the mobile devices I was testing in as it was before, and worked on desktop browsers with the wide.css layout. I just had to add a few extra divs some HTML5 semantic markup to get some extra styling hooks. Success! And I'm continuing to be lazy since I'm reusing as much code as possible. There is a disadvantage to this technique though. It requires the download of two files. I could have combined them into one file, but that would've introduced some hacks as described in the Dev.Opera document. I am consoling myself by saying that wide.css is actually a very small file, and CSS files are downloaded in parallel anyway, so the impact shouldn't be huge. But that said, it is something to think about.

Wrapping it up

That just leaves us with one beast — the old nemesis — Internet Explorer. We've gone through all this trouble to support all sorts of crappy old mobile browsers, it only makes sense that we don't leave IE behind. Now, IE doesn't understand media queries, so how do we serve it wide.css? Turns out, we can use IE against IE, thanks to it's own conditional comments! The final markup looked like this:

<link rel="stylesheet" type="text/css" href="narrow.css" />
<!--[if lt IE 9 ]>
<link rel="stylesheet" type="text/css" href="wide.css">
<!--[if (gte IE 9)|!(IE)]><!-->
<link rel="stylesheet" type="text/css" media="all and (min-width: 400px)" href="wide.css">

Let's dissect that. Firstly, serve the old narrow.css the way it was being served. No change there. Next, detect if the browser is IE and the version is less than 9. Why 9? Because MS is promising that IE9 has got media query support, so we'd rather use that over blindly serving the file to IE. If it is less than IE9, serve it wide.css no questions asked. If the IE version is 9 or greater, or if it's a non-IE browser, serve it wide.css with the media query.

This post is bound to be outdated before it's even out, since MS is launching Windows Mobile 7 or whatever it is called with a very uninteresting version of IE which is purportedly somewhere between 7 and 8. But I don't know yet how the browser is identifying itself, and hence can't exclude wide.css from it. If you find out, please leave me a comment, and I'll update my post with your findings.

In the end

We've successfully managed to:

  • Target mobile phones, new and old.
  • Match device orientation for newer phones.
  • Apply different CSS to desktop browsers.
  • Get it working even for the thing that is IE.
  • Keep Luke happy.
  • Done all of this without using any pesky JavaScript or server-side UA sniffing.
  • Continued to be lazy.

This has not been without drawbacks:

  • Two CSS files are downloaded to wide clients. To make things worse, some parts of narrow.css are overridden in wide.css, making the double-download even more painful.
  • Mobile IE gets the wide version, mostly because I don't know too much about it.

This works for me for the moment though. Would love to hear your thoughts.

Like this post? I blabber on a lot about this kind of stuff on the twitters. Follow me there if you like.