By default, your typical mobile browser (Mobile Safari, Android browser) will pretend to have a “desktop-sized” viewport when it renders a web page. The web page will appear zoomed-out and tiny, and you’ll have to zoom in to read most of the text. The photo below shows three Android devices side-by-side, rendering a plain web page, so you can compare what they look like. From left to right, the devices are:
- Samsung Galaxy Y running Android 2.3. Screen size: 240px x 320px; physically 4.5cm x 6cm (~135 pixels/inch)
- Samsung Galaxy Ace running Android 2.2. Screen size: 320px x 480px; physically 4.9cm x 7.8cm (~166 pixels/inch)
- Motorola Defy+ running Android 2.3. Screen size: 480px x 854px; physically 4.5cm x 8.1cm (~271 pixels/inch)
If you are building for mobile, you probably don’t want this. You can use the
viewport meta tag in the head of your document to tell the browser to render at the actual width of the device.
<meta name="viewport" content="width=device-width">
The photo below shows the Android browser rendering a page with this meta tag:
Wait, what? They all show a screen width of 320px? Don’t those three devices all have different screen sizes? Shouldn’t they go 240px, 320px, 480px? I paid for a high-resolution 480x854px phone, damn it; where are my pixels?!
Let’s have a look in Opera Mobile:
240px, 320px, 320px. O-kaaaay…
What’s going on is that the device/browser manufacturers are trying to be helpful. The screen of the Defy+ phone is physically narrower than the Galaxy Ace, but it squeezes more pixels into each square inch of the display. Using one device pixel for each CSS pixel of a 480px-wide layout on a 4.5cm-wide screen would result in some very small text. So there is a zoom factor to adjust the way the page is rendered. In Opera Mobile, you can see (and change) the zoom factor by going to Settings → Zoom. The picture below shows the zoom zettings on these three phones:
You can see that on the third device, the zoom level is set to 150%, which is how 480px gets turned into 320px. If I set that to 100%, then the results are as expected:
The stock Android browser has a similar setting, found under Menu → More → Settings → Default zoom. In the Android browser your choices are limited to “Close”, “Medium”, and “Far”.
By default, all of the Android browsers had their zoom set to “Medium”. By setting the first device to “Close”, and the third device to “Far”, I can persuade the devices to render at their natural hardware resolution.
This doesn’t actually help me as a web developer, though, because I can’t force a user to change their browser settings.
This is where the
target-densitydpi=device-dpi viewport setting comes in. With this setting, you can indicate that you want a specific page to render at the device’s native hardware resolution:
<meta name="viewport" content="width=device-width, target-densitydpi=device-dpi">
If I set all the android browsers back to the default “Medium” zoom, and set the zoom on Opera Mobile back to 150% for the Defy+, then I get the following results when I view a page with the target-densityspi property set:
Looking back at all these photos, you can see that the test pages show a second value, pixel ratio, which comes from
window.devicePixelRatio. This should (I think) tell you how many device pixels go into a CSS pixel. For example, if the screen width is 320px, and
window.devicePixelRatio reports 1.5, then the device is actually using 480 hardware pixels to render the 320px-wide layout. This means you’re working with a high-resolution display, so text will be crisper, and you can use high-resolution versions of images.
But although this works as expected in Opera Mobile, the Android browser seems to always report the default value for
window.devicePixelRatio no matter what the actual value is on the page, as specified either by the user’s zoom setting, or forced by the
target-densitydpi=device-dpi property in the viewport meta tag. Android browser on the Defy+ keeps reporting a value of 1.5 even when it is using a 1:1 pixel mapping, and the Galaxy Y keeps reporting 0.75. Likewise,
min-device-pixel-ratio media queries are triggered from these default values, not from the active pixel ratios. This might be a problem when it comes to loading different versions of image assets.
I don’t recommend using this
target-densitydpi viewport setting in real life. It may be tempting to force a 1:1 pixel mapping so that you can deliver wider layouts to high-resolution devices, but a typical 480px-wide layout looks uncomfortably small on a 4.5cm-wide screen. The point of high-resolution screens on mobile devices is to make the content look sharper at the same physical dimensions, not to keep squeezing more and more content into that space. I’m pretty sure that’s why Mobile Safari on iOS does not support this property in the viewport meta tag at all.
At the other end of the scale, there are plenty of low-resolution devices out there that use a devicePixelRatio of less than 1 to make their small viewports appear wider. The inexpensive Samsung Galaxy Y is a great example. It has a screen width of 240px, but pretends to be 320px wide by rendering 4 CSS pixels are across every 3 hardware pixels. This makes 320px-wide layouts look a bit blurry, but that’s better than breaking; many sites consider 320px as their mobile baseline (hello, iPhone), and ignore smaller viewports.
(Aside: “Nobody really uses a 240×320 screen” is the 2012 version of “nobody really browses the web on their phone.” Actually, yes they do. But because of this pixel scale factor, you might not notice them in your analytics.)
Basically, don’t set
target-densitydpi unless you really know what you’re doing, and are prepared to consider the actual physical dimensions of your CSS pixels, which means testing on a lot of devices. Otherwise, trust the mobile browser to render those CSS pixels at a size that is comfortable for most users. If the user wants more control, the browser zoom setting is just a couple of taps away.
6 Replies to “Goldilocks and the Three Device Pixel Ratios”
What’s going on is that in Android, the design width of these screens is 320px, and the UI is scaled to the physical width.
That makes them all normal sized screens, two at medium resolution, and one at high resolution.
You have the same thing when developing native apps, in that you’d normally not uses pixels (px) but scalable pixels (dp) for sizes in your layout, for a nominal screen of 320×480dp.
http://developer.android.com/guide/practices/screens_support.html#testing has the physical resolutions that can be expected.
It’s a feature, not a bug 😉
So in Android browser the value of
window.devicePixelRatioreflects only the underlying value of
DisplayMetrics.densityin the Android API, which is is fixed by the hardware. In Opera, window.devicePixelRatio reflects the actual pixel density for the page that is being displayed, taking into account the current zoom level. Opera’s implementation is more useful for me as a web developer, but given that the property is called
devicePixelRatio, Android browser’s interpretation may be more semantically correct.
Regardless of which one is technically correct, the fact that they behave differently is a pain in the nuts.
Great article, just one question – you wrote:
“You can use the viewport meta tag in the head of your document to tell the browser to render at the actual width of the device.”
What is the ‘actual width’ of the device for each of the phones you used? It’s not 320px so why do they all scale themselves to give a width of 320px?
Good question Jeremy, id like to know the same thing! I think there has to be some type of code that makes its stretch or not according to screen size
Great article! Would be nice to see a follow-up on how the various devices/browsers handle all this in combination with some of the other absolute length units (e.g. cm and inches).
Great breakdown which leaves me starving for more device pixel ratio/css pixel/device pixel information as this stuff gets fairly confusing!
Comments are closed.