It occurs to me that my recent post about
devicePixelRatio was a bit cryptic and short. So I will givde some extra examples in this post. Also, I did some additional research that takes
screen.width into account.
Remember the definition:
devicePixelRatio is the ratio between physical pixels and device-independent pixels (dips) on the device.
devicePixelRatio = physical pixels / dips
Here is the test page. It shows the
devicePixelRatio of your browser. Note that on most desktops and laptops it’s still 1. Retina laptops show 2 in Safari and Chrome 22, 1 in Chrome up to 21(?) and Opera, and undefined in IE and Firefox. (By the way, the 2 value is not always correct. See below.)
Similarly, non-retina iOS devices will show 1, while retina devices will show 2. That’s because the actual number of pixels has doubled, but the browser pretends it hasn’t so as not to upset the carefully crafted 320px-wide layouts people made especially for the iPhone. Sites with a meta viewport of device-width should not suddenly become 640px wide, after all. So the dips width remained at 320px, and 640/320 = 2.
On iOS the situation is relatively simple: there are only the values 1 and 2. On most other platforms the situation is also simple because the real physical pixel count is equal to the dips count, which gives a
devicePixelRatio of 1.
The main complications come from Android; or rather, from the many modern devices that sport a retina-like display that has significantly more pixels than just 320, most of which (in my collection) run Android.
In fact, Google’s Nexus One was the first device ever, as far as I know, to use dips; even before the iPhone. Meanwhile the Galaxy Nexus and the Galaxy Note both also sport retina-like displays. A closer look at these three devices is instructive.
The Nexus One has a physical resolution of 480x800, but the Android WebKit team decided that the optimal width for a web page viewed in portrait mode was still 320px. Thus the dips abstraction layer remained 320 pixels wide, and
devicePixelRatio became 480/320 = 1.5.
On the same phone, Opera Mobile reached the same conclusion. Its dips layer is also 320px wide, and its
devicePixelRatio is also 1.5.
(Incidentally, the BlackBerry Torch 9810 with OS7 also has 480 physical pixels, and the BlackBerry WebKit team decided to stick to a
devicePixelRatio of 1. In retrospect it might have been better if they’d moved to 1.5, too; 480px wide sites are somewhat hard to read on the Torch’s display.)
The Galaxy Nexus has a significantly upped pixel count of 720x1200. The Android team decided to up the width of the dips layer, too, to 360px. This makes
devicePixelRatio 720/360 = 2. The Chrome team decided on the same, as did TenCent’s QQ browser.
Opera, however, disagreed, deciding to stick with a dips width of 320px. This yields a
devicePixelRatio of 720/320 = 2.25. (When I saw this value I thought Opera had a bug here, but it does not. The value is perfectly consistent with the physical and dips widths.)
The Galaxy Note, finally, has a physical pixel count of 800x1200. Here all browsers decided on the same ratio as on the Galaxy Nexus: Android WebKit, Chrome, and QQ stick with 2 (which means a dips width of 400px), while Opera sticks to 2.25, arriving at the slightly odd dips width of 356px.
You still with me? The various browsers are essentially all playing their own game here. That’s perfectly fine as long as they report the correct
devicePixelRatio works fairly consistently across browsers (see my earlier report for the exceptions); and the relation between
devicePixelRatio, physical pixels, and dips is a purely mathematical one, meaning that if you know two of them you can calculate the third.
But how do we find either the dips width or the physical pixel width?
Dips are easy: give your page a
<meta name="viewport" content="width=device-width">, read out
document.documentElement.clientWidth, and most browsers will give you the width of the layout viewport, which now equals the dips width. (Here is the necessary compatibility table.)
If you can’t use this calculation, though, things get more hairy. We’re forced to use the values given by
screen.width. But what do these values mean?
If you think their meaning depends on the browser, congratulations! You’re starting to understand the mobile world.
screen.widthgives the width in dips. So both a retina and a non-retina iPad report 768 in portrait mode.
screen.widthgives the width in physical pixels; 480, 720, and 800, respectively. All browsers on the devices use the same values. (Imagine if some browsers on the same device used dips and others physical pixels!)
Vasilis has a nice theory: Apple added pixels because it wanted to make the display crisper and smoother, while the Android vendors added pixels to cram more stuff onto the screen. It’s one way of looking at these differences: it explains why Apple emphasises the continuity from non-retina to retina, while Android concentrates on the raw pixel count.
So what do other browsers on other OSs think? The problem here is that I can use only those browsers that have a different dips count than physical pixel count, and I don’t have too many of those. The single one I could get a clear reading from, IE9 on the Nokia Lumia Windows Phone, agrees with Android and gives the physical pixel count in
screen.width. It does not support
devicePixelRatio, though, so I can’t test it fully.
So my conclusions on the mobile side are:
devicePixelRatiois mostly trustworthy on most browsers.
screen.widthto get the physical pixel count.
devicePixelRatioto get the dips count.
Here’s a nice browser compatibility conundrum. It’s even worse than normal because I’m not sure what the answer should be.
Finally, a word about the new retina MacBook. Its
devicePixelRatio is (supposed to be) 2, but the situation is more complex than you’d think, because you can change the resolution. What you change is the size of something that’s kind of a dips layer (though not really). In any case, the point here is that
devicePixelRatio doesn’t change.
The physical pixel count of a retina MacBook is 2800x1800, and the out-of-the-box resolution is 1400x900. Counting this resolution as kind-of a dips layer, a
devicePixelRatio of 2 is correct.
Point is, when you change the resolution to 1920x1200
devicePixelRatio remains 2. Strictly speaking that’s wrong — it should become 1.5. However, you could also argue that a MacBook’s resolution is not the same as a dips layer, in which case
devicePixelRatio has a different definition on desktop/laptop. (Which one? Dunno.)
In any case, what Apple has done here is standardise on only two
devicePixelRatio values: 1 and 2. If you see 2 you know that you can serve retina-optimised images, while a 1 says you should serve normal images.
I’m not a big fan of serving special retina images because it makes the web too heavy — especially over a mobile connection. Nonetheless people will do it.
If you use this sort of detection, please remember to build in a case for when
devicePixelRatio is neither 1 nor 2 but, for instance, 1.5 or 2.25.
Update: Turns out Opera's value depends on the zoom level. And I wouldn't be surprised if the same goes for other browsers. I did not test this. Maybe later.
Upcoming speaking gigs: