Images in a responsive Web

Fluid content and any screen size are foundational notions of responsive Web design. However, figuring out how to best implement images in a responsive Web site is a problem with as many questions as answers.

As the viewport changes size and different media queries are applied to adjust the layout of the content, images often change size from small to large and anywhere in between. Additionally, pixel dense displays, which look best when images of twice the width and height are served, are becoming increasingly more common. To account for this change in size and pixel density, some sites have begun to serve files with different dimensions to different users. But therein lays the problem: who should get which image?

To solve for this void with standard HTML several people have come together to propose the picture element. For the Microsoft.com homepage we came at it from a few different angles. These and others are listed below.

For some graphics it is possible to use icon fonts or SVG to forego images and have graphics that scale beautifully. We chose to use an icon font for some icons on the page but I will save that for another post.

2x images

To account for high density displays, some people's first instinct is to just serve everyone a 2x version of the image. Unfortunately this requires most users to download a larger image than is needed. In some cases however, this might be an acceptable solution.

1x images

Other people take a much simpler approach and continue to serve everyone a 1x version of the image. While this optimizes for quick downloads, it can result in images looking pixelated and blurry on HD displays. In some cases however, you may find that it is not worth the extra bytes to download the higher quality image.

1._x images

You can always try meeting in the middle with 1.5x images. They are not as blurry as a 1x image on a HD display and won’t take quite as long to download as a 2x image. You can fine tune the balance of blur and file size by trying other sizes: 1.2x, 1.4x, 1.7x, etc.

See full-size image for best comparison. Screenshot from iPad with Retina display.

Picture element polyfill

When a single image size doesn’t do the trick, you can consider a whole load of other options with a picture polyfill. The polyfill example include in the W3C proposal is a branch of the picturefill work done by Scott Jehl. The largest advantage I see to this becoming a standard element, besides not having to include another JavaScript file, is being able to have the browser request images based of preferences set by the user (to account for low bandwidth situations). For the Microsoft.com homepage we looked at the work Scott had done and the W3C proposal and rolled our own version to meet our requirements. We are making our branch publicly available to download.

Some of the things we added:

  • Allow specific images to load before the entire DOM is ready
  • Allow specific images to only load after the rest of the page loads
  • Disable swapping out of specific images when needing to shrink
  • Disable swapping out of specific images when needing to grow
  • Wait for the new image to complete downloading before updating the img element
  • Load images based on the physical device pixel width
  • Render inner HTML of noscript element if no sources are supplied

The following is an example of the markup used to render the Microsoft logo in our header:

<div data-picture data-alt="Microsoft">
    <div data-src="http://i.s-microsoft.com/.../logo-lg-1x.png"></div>
    <div data-src="http://i.s-microsoft.com/.../logo-lg-2x.png" data-media="(min-device-pixel-ratio: 2.0)"></div>
    <div data-src="http://i.s-microsoft.com/.../logo-sm-1x.png" data-media="(max-width: 539px)"></div>
    <div data-src="http://i.s-microsoft.com/.../logo-sm-2x.png" data-media="(max-width: 539px) and (min-device-pixel-ratio: 2.0)"></div>
    <noscript><img src="http://i.s-microsoft.com/.../logo-lg-1x.png" alt="Microsoft" /></noscript>
</div>

The following is an example of the markup used to render a hero image:

<div data-picture data-alt="Meet Windows 8." data-disable-swap-below>
    <div data-src="http://i.s-microsoft.com/.../1600x540.jpg"></div>
    <div data-src="http://i.s-microsoft.com/.../1024x346.jpg" data-media="(max-device-pixel-width:1024px)"></div>
    <div data-src="http://i.s-microsoft.com/.../600x203.jpg" data-media="(max-device-pixel-width:600px)"></div>
    <div data-src="http://i.s-microsoft.com/.../480x162.jpg" data-media="(max-device-pixel-width:480px)"></div>
    <noscript><img src="http://i.s-microsoft.com/.../1600x540.jpg" alt="Meet Windows 8." /></noscript>
</div>

To learn more about our picture polyfill view our branch. We hope to have more documentation posted soon.

Image types and scaling

When analyzing images on the homepage we noticed a few different things. These may not be all inclusive and are only meant to represent our page at this point in time.

Images contain different types of content:

  • Logo
  • Text
  • Photograph
  • Illustration
  • Mixture of above

We then looked to see how each of these image types looked on a HD display.

See full-size image for best comparison. Screenshot from iPad with Retina display.

As you can see the 1x versions of all types appear to look considerably worse than their 1.5x or 2x counterparts when viewed on a HD display. We took this into account and then looked at how the images would rescale on the page.

Images sizes change for different reasons:

  • Fixed size, never changes
  • Two different sizes depending on the viewport width
  • Width is equal to the viewport width but then reaches a defined max-width (1600px)
  • Width changes proportional to its container instead of the viewport width

With this information we were able to start considering how to implement images on different areas of the page depending on what type of content they contain and how they rescale.

What we came up with so far:

  • Header logo – 4 sizes with picture polyfill based on viewport width and pixel density
  • Icons – Web font with fallback to 1x images
  • Hero image – 4 sizes with picture polyfill based on physical screen width
  • Product logos in tile overlay – 2x images
  • For home / For work images – Images scaling from 1.4x to 3.5x (at 320px viewport) depending on viewport width
  • News Center images – 1.2x images
  • Footer logos – 2x images
  • Footer Microsoft logo – 2 sizes with picture polyfill based on pixel density

As you can see, we are doing a bit of everything depending on the specific case; which I think is a good thing. We will be revaluating our approach as more HD displays come to market so expect things to change a bit.

Which is better 1x, 2x, 1._x or picture element?

Let’s take the Microsoft.com footer image as an example. For these images we opted for 2x images (52 x 52 scaled to 26 x 26) to ensure quality on pixel dense displays while still keeping the file size small.

See full-size image for best comparison. Screenshot from iPad with Retina display.

As you can see the difference between the 1x and 2x images is 648 bytes. Some people might think that the picture polyfill would work great here; however it takes approximately 500 bytes worth of markup (not including our JavaScript library) to render the mock picture element. This would require an even larger download for HD users and a download of almost the same size as the 2x for non-HD users.

Device width vs. viewport width

On most phones and tablets viewport width and device width are the same thing. However, on laptops and desktops viewport is equal to the inner width of the browser window and device width is equal to the display size. Device width can get messy when using multiple monitors as browsers implement this differently. IE will report the width of your main display while Chrome and Firefox will usually report the width of the current display (sometimes it seems to lose track of which display it is on).

How to obtain the device pixel width with JavaScript (download source):

/*! GetDevicePixelWidth | Includes: GetDevicePixelRatio | Author: Tyson Matanich, 2012 | License: MIT */
(function (n) {
    n.getDevicePixelWidth = function (t) {
        t = t || !1;
        var i = t == !1 || n.screen.width > n.screen.height ? n.screen.width : n.screen.height,
            r = t == !1 || n.innerWidth > n.innerHeight ? n.innerWidth : n.innerHeight;
        return r > i && (i = r), i * n.getDevicePixelRatio()
    }
})(this);

How to obtain the viewport width with JavaScript (download source):

/*! GetViewportWidth | Author: Tyson Matanich, 2012 | License: MIT */
(function (n) {
    n.getViewportWidth = function () {
        var u, f = n.document,
            t = f.documentElement,
            i, r;
        return n.innerWidth === undefined ? u = t.clientWidth : n.innerWidth > t.clientWidth ? (i = f.createElement("body"), i.id = "vpw-test-b", i.style.cssText = "overflow:scroll", r = f.createElement("div"), r.id = "vpw-test-d", r.style.cssText = "position:absolute;top:-1000px", r.innerHTML = "<style>@media(width:" + t.clientWidth + "px){body#vpw-test-b div#vpw-test-d{width:7px!important}}</style>", i.appendChild(r), t.insertBefore(i, f.head), u = r.offsetWidth == 7 ? t.clientWidth : n.innerWidth, t.removeChild(i)) : u = n.innerWidth, u
    }
})(this);

Detecting a high-density display

In WebKit based browsers, the pixel density of the display can be detected with JavaScript using window.devicePixelRatio. An iPhone 4s will return a value of 2. However there can be a bit of inconsistency in various browsers.

In Internet Explorer 10 you will need to calculate the device pixel ratio, which at first may seem annoying but in the end provides much more flexibility.

A calculation for device pixel ratio in Internet Explorer 10:

window.screen.systemXDPI / window.screen.logicalXDPI

A calculation for device pixel ratio accounting for zoom in Internet Explorer 10:

window.screen.deviceXDPI / window.screen.logicalXDPI

Obtaining device pixel ratio across browsers (download source):

/*! GetDevicePixelRatio | Author: Tyson Matanich, 2012 | License: MIT */
(function (n) {
    n.getDevicePixelRatio = function () {
    var t = 1;
    return n.screen.systemXDPI !== undefined && n.screen.logicalXDPI !== undefined && n.screen.systemXDPI > n.screen.logicalXDPI ? t = n.screen.systemXDPI / n.screen.logicalXDPI : n.devicePixelRatio !== undefined && (t = n.devicePixelRatio), t
    }
})(this);

A pixel is not always a pixel... what?!

Most of the media queries work great if you need to download images based on the viewport or device width; however both of these use CSS pixels of the logical kind not the actual physical pixels. When you need to use the actual physical pixels you will have to use a bit of JavaScript (see code examples above).

So what’s the answer?

As you can see there are a lot of different ways to approach the issue, and probably several more that I have not covered or thought of. It will be interesting to see what solutions are produced in the next few years as more HD devices are released and more Web sites start to serve HD images.