News

How to serve images to hi-res screens with CSS

Learn how to utlilise CSS media queries to help deliver pixel heavy pictures for devices with retina displays

picturefill

The latest iPad has a 2,048 x 1,536 pixel resolution at 264 pixels-per-inch (PPI) screen, the iPhone 4 and iPhone 4S have greater pixel density: 960 x 640 pixel resolution at 326 PPI. It isn’t just Apple’s devices that can be considered high-resolution, even if they don’t boast marketing terms like Retina display.

The HTC One X uses a 4.7-inch display, with 312 PPI for example; and even Sony’s latest handheld gaming device, the PlayStation Vita, has a 220 PPI screen. We now have Apple’s Retina display MacBook Pros too, at 220 PPI. It’s easy to see where this is going; pretty soon high-resolution/PPI displays will be the norm on all devices that access the web. The pickle at present is that while text looks beautiful on these devices, web images usually don’t. As high-resolution devices often have two dots per CSS pixel, the result is that images end up looking blurry.

Thankfully, there are a number of things we can do to address this issue. Let’s take a look at how CSS3 media queries and icon fonts can ease our pain, and then deal with the obvious elephant in the room; delivering alternate images in markup.

CSS minimum-resolution

CSS3 media queries can be used to target high-resolution/PPI screens; we can use them to serve different background images depending upon the pixel density of the device. By now you are no doubt familiar with the media query syntax. Let’s suppose we want to show two images, one for standard screens and one for high-PPI devices. We can go ahead and use the old min-resolution property:

001 @media (min-resolution: 96dpi) {
 002  /* rules here would apply to a normal screens */
 003 }
 004 @media (min-resolution: 192dpi) {
 005  /* rules here would only apply to high-
 resolution screens */
 006 }

About those iDevices

Sadly WebKit (iOS, Android, Safari, Chrome and others) hasn’t implemented min-resolution, and so we need to use WebKit’s own property. It uses a simpler system where the value represents the pixel density of the device. A ‘normal’ screen will have a pixel ratio of one. An iPhone 4 or similar, on the other hand, could be targeted like this:

001 @media (-webkit-min-device-pixel-ratio: 2) 
 002 {
 003  /* Webkit high-resolution specific rules 
 here */ }

Other WebKit devices

While the WebKit prefixed rule also targets Android devices, many Android handsets (although still enjoying high-resolution screens), don’t have quite the same pixel density as something like an iPhone 4, 4S or new iPad – and none have anything near Retina display. Therefore, you may want to lower the threshold to include more devices.

001 @media (-webkit-min-device-pixel-ratio: 
 1.5) {
 002   /* lower threshold rules here */ v
 003 }

Mozilla is different

Another vendor, another expression! Mozilla uses a comparatively odd syntax. If you want to target high-resolution/PPI devices that might be using a Mozilla based browser such as Firefox, pay special attention as the minimum part of the expression is written first, followed by a double-hyphen:

001 @media (min--moz-device-pixel-ratio: 2) {
 002   /* Mozilla rules here */
 003 }

Opera syntax

Unbelievably, Opera uses a different syntax again. While similar in syntax to WebKit’s version (albeit with an -o- prefix as opposed to -webkit-), Opera has opted for a ratio representation of the pixel density. For example instead of writing 2 as the value (as you would with WebKit), it must be presented as 2/1.

001 @media (-o-min-device-pixel-ratio: 2/1) {
 002  /* Opera rules here */
 003 }

The W3C way?

Owing to the prevalence of the -webkit- prefixed version of this media query, you may be forgiven for assuming that the W3C would follow suit, declaring an unprefixed version of that syntax as the global web standard. However, as minimum-resolution already existed, they have not done this and have instead opted to add a new unit of measure, dots per pixel, written as dppx in CSS. Below is what the syntax is likely to be styled as in the future:

001 @media (min-resolution: 2dppx) {
 002  /* rules here will apply to high
 resolution media in the future */
 003 {

Full vendor stack

Let’s go right ahead and put all these previous vendor versions together with the likely W3C implementation (dppx), into one media expression that we can use to bulletproof this technique whenever we implement it. The user-agent (whichever browser you are using) will happily ignore any expressions that don’t apply and render the one most relevant, ensuring cross-browser functionality.

001 @media (min-resolution: 192dpi), (-webkit-
 min-device-pixel-ratio: 2), (min--moz-device-
 pixel-ratio: 2), (-o-min-device-pixel-ratio: 
 2/1), (min-device-pixel-ratio: 2), (min-
 resolution: 2dppx) {
 002  /* High Resolution styles go here */ 
 003 }

A use case

Let’s suppose we wanted two versions of a site logo, one for high-resolution screens and another for everything else. Using our full vendor stack from the previous step to achieve parity across browsers, here is an example of how we could set one image for normal screens and another for high pixel-density displays:

001 .logo {
 002    background-image: url('../img/logo.
 png');
 003 }
 004 @media (min-resolution: 192dpi), (-webkit-
 min-device-pixel-ratio: 2), (min--moz-device-
 pixel-ratio: 2), (-o-min-device-pixel-ratio: 
 2/1), (min-device-pixel-ratio: 2), (min-
 resolution: 2dppx) {
 005   .logo {
 006    background-image: url('logo@2x.png');
 007   } 
 008 }

Making it fit

While that will display the correct image, if we set a high-resolution background image as our logo, and the logo div it sits in is smaller in pixel size than the image, ordinarily it will overflow the container. Thankfully, there is a CSS3 property that can solve our issues. We can use the background-size property with a px value or contain value to make it fit:

001 @media (min-resolution: 192dpi), (-webkit-
 min-device-pixel-ratio: 2), (min--moz-device-
 pixel-ratio: 2), (-o-min-device-pixel-ratio: 
 2/1), (min-device-pixel-ratio: 2), (min-
 resolution: 2dppx) {
 002   .logo {
 003     background-image: url('logo@2x.png');
 004     background-size: contain;
 005   } 
 006 }

Background-size options

The background-size property can also take numerical values. Using pixels for size can be useful if you want your images to stay a set size within a fluid container. The width is given first, then height. If no height is given, auto is assumed for the height. A percentage can also be used and this will render the image as a percentage relative to the container.

001 .background-size-dimensions {
 002   background-size: 300px 300px;
 003 }

Naming conventions

You’ll notice in step 9 that the high-resolution logo file name is the same as the standard resolution targeted one, except it has been suffixed with ‘@2x’ before the file extension. This is a naming convention used by Apple when targeting their Retina displays. It’s actually not necessary, but many pre-existing JavaScript solutions use this convention, so it may be something that is worth adopting.

001 .standard {
 002  background-image: url(‘image.png’);
 003 }
 004 .high-resolution {
 005   background-image: url(‘image@2x.png’);
 006 }

Icon fonts

If you want to provide scalable icons for a design, there is a better option than images: icon fonts. There are a growing number of icon fonts available, and they can be included in a design using the @font-face rule. You’ll need a version of your icon font in WOFF, SVG, EOT and TrueType to cover all browsers. Then use the @font-face rule to include it.

001 @font-face {
 002   font-family: ‘hr’;
 003   src: url(‘fonts/hr.eot’);
 004   src: url(‘fonts/hr.eot?#iefix’)  

Setting up defaults

In this example we are using fonts from IcoMoon (which can be found here: keyamoon.com/icomoon/#toHome), which are mapped to characters. Therefore, we can add them by adding a particular class to the icons needed in the markup. First of all, we’ll define styles that will be particular to all the icons. This will cover us wherever the classes are ordered among others.

001 [class^=”icon-”]:before, [class*=” icon-
 ”]:before {
 002   font-family: ‘hr’;
 003   font-style: normal;

Adding with class

We can now add the relevant icon wherever we want by adding the relevant class. The icon is inserted before the element it is added to. If you want the icon to appear afterwards simply amend the pseudo selector from before to after.

001 .icon-chrome:before {
 002   content: “21”;
 003 }
 004 .icon-firefox:before {
 005   content: “22”;
 006 }
 007 .icon-opera:before {
 008   content: “23”;
 009 }
 010 .icon-globe:before {
 011   content: “24”;
 012 }
 013 .icon-check-alt:before {
 014   content: “25”;
 015 }

Adding icons inline

Adding the icons inline without a pseudo selector is not a good idea semantically, as the content is meaningless. If you wish to, remember to use character entities for each number. Otherwise the number will appear as is. Here, we are adding all the icons in a row.

001 /* CSS */
 002 .icon-insert {
 003   font-family: ‘hr’;
 004   font-style: normal;
 005   speak: none;
 006   font-size: 5em;
 007 }
 008 <!-HTML -->
 009 <p class=”icon-
 insert”>&#x21; &#x22; &#x23; 
 &#x24; &#x25;</p>

Inserting as data attribute

You can also use data attributes to insert icon fonts. This lets you keep the icons out of the actual page content itself. Remember that any attribute beginning ‘data-‘ in HTML5 is perfectly valid code, so it isn’t going to sour the markup for validators. Insert the icons in the markup in the same way as the previous step: using character entities.

001 /* CSS */
 002 [data-icon]:before             {
 003   font-family: ‘hr’;
 004   content: attr(data-
 icon);
 005   speak: none;
 006   font-size: 5em;
 007 }

Font flexibility

By using fonts, the icons will scale to any resolution and can have colour, text-shadows and even experimental ‘-webkit-mask-image’ values (for WebKit browsers) added. What’s more, compared with images, the file size is tiny. For the five icons used here, the largest font that would be served is just eight KB (before gzip).

001 [class^=”icon-”]:before,  [class*=”icon-”]:before {
 002   font-family: ‘hr’;
 003  font-style: normal;
 004  speak: none;
 005  font-size: 5em;
 006  color: green;
 007  -webkit-mask-image:-webkit-linear-gradient(white
 ,rgba(0,0,0,0.7));
 008   text-shadow:0 -1px black;
 009 }

Using Picturefill

There is no proper way to add alternate high-resolution images such as photos in markup yet. One solution is to use Picturefill, a JavaScript solution that follows a markup pattern similar to the HTML5 <video> tag. After adding the Picturefill JS file within the markup, alternate images can be specified using media query style expressions. Note the slightly different syntax present for the media query.

001 <div data-picture data-alt=”An 
 image, whose source changes depending 
 upon the media query style expressions”>
 002 <div data-src=”normal.jpg”></div>
 003 <div data-src=”high-resolution.
 jpg” data-media=”(min-resolution: 
 192dpi),(-webkit-min-device-pixel-
 ratio: 2),(min--moz-device-pixel-
 ratio: 2),(-o-min-device-pixel-ratio: 
 2/1),(min-device-pixel-ratio: 2),(min-
 resolution: 2dppx)”></div>
 004 <!-- Fallback content for non-JS 
 browsers. Same img src as the initial, 
 unqualified source element. -->
 005  <noscript><img src=”normal.jpg” 
 alt=”An image, whose source changes 
 depending upon the media query style 
 expressions”></noscript>
 006 </div>

Adaptive images

A great tool for delivering images on a responsive website can also be used to deliver high-resolution images where needed. Head over to adaptive-images.com and after installing (you are going to need access to your server’s .htaccess file), alter the standard line of JavaScript used, to:

001 < script>document.
 cookie=’resolution=’+Math.max(screen.
 width,screen.height)+(“devicePixelRatio” in window ? “,”+devicePixelRatio : 
 “,1”)+’; path=/’;< /script>

Uploading high-resolution files

When using Adaptive Images for high-resolution images, you’ll probably want to limit which images are processed by it. You can limit this by altering the .htaccess file. For example, if we didn’t want Adaptive Images to resize images inside a folder called no-thanks (folder addresses are root relative) we could add a line that looks like this:

001 RewriteCond %{REQUEST_URI} !no-thanks

Fit and forget

One great thing about Adaptive Images is it works by re-using existing images where possible for high-resolution screens. For example, if viewing on an iPhone 4 in portrait, Adaptive Images will multiply the CSS pixel-width of the device (320px) by the pixel density reported (2) and use that value (640px) to check and use an image matching that size where possible.

SVG files?

If you have vector images, another option is to consider exporting them SVG(Z) files. Like icon fonts, SVG images (whether background or inline) will scale well regardless of screen size and pixel density. Browser support is generally good (IE9+) and file size is typically far lower than a PNG/JPG equivalent.

001 .svg .logo {
 002 background-image: url(‘logo.svg’);
 003 }

AUTHOR: Ben Frain

×