• IMHO
Stephanie Walter May 16th, 2012

The Mobile Web: CSS Image Replacement for Retina Display Devices

I see more and more devices that have a pixel ratio bigger than 1.5, even 2. My Galaxy Nexus for example has a pixel ratio of 2 and so do the latest versions of the iPhone and iPad. Retina display seems to be the next evolution and next challenge for us as designers. introduction Native mobile app designers have already learned how to take advantage of those devices with high pixel ratios to display bigger images with better quality, so as to enhance user experience. They are used to creating the images in both normal and retina @2x sizes for the iPhone, and creating 4 sets of drawables in 4 different sizes for Android devices. With the iPad 3 also having retina display, it is definitively something that will be harder to avoid from now on. In this article, you will see how to use some CSS3 tricks in the field of image replacement to serve images with better quality to those high resolution devices.

Story Behind the Code

It all began when I was creating a jQuery Mobile application for the iPhone. The idea was to make a full HTML5 jQueryMobile app, and to embed it in a “native shell”, using Phonegap. For this application, I created a bottom tab-bar that was imitating the native iOS tab-bar, and also a header with a logo image in it. Both the header and footer were HTML elements that used image replacement techniques to display the icons and logo. When I tested the application on the iPhone 4S, I saw that the logo and the icons were highly rasterized and looked pretty ugly.

The Demo

The demo I re-created a fake application page similar to the iOS native style so you can see what is going on. Whether you have a retina device or not, you can test it here with your phone. You can see the demo here. You can also download the code here. As I said, if you load the page on a non retina device, it will look good. If you load it on a retina device, the images get rasterized. This is due to the pixel ratio being 2, so the image is multiplied by two and stretched by the device, creating this unclean rendering. Here are some screenshots of the demo on iPad 3, iPhone 4 and Galaxy Nexus with the images being rasterized: Galaxy Nexus: Android rasterized iPhone 4: iPhone rasterized iPad 3: iPad 3 rasterized

CSS Image Replacement Techniques

In this demo, I used different techniques for replacing images that will have varying consequences when we will want to change for retina images. The first image we replace is in the logo, being sure to only set the height of the element. The HTML looks like this:
<div class="ui-header"> <h1> My logo </h1></div>
The CSS like this:
.ui-header h1{

color:#fff;

display: block;

outline: 0 none !important;

overflow: hidden;

margin:0;

text-align: center;

text-overflow: ellipsis;

white-space: nowrap;

text-indent:-9999px;

background:url(img/logo.png) no-repeat center center;

height:33px;

}
Again, what’s important here is that we give it height, but no width. The second technique is to use the delete button. We want to keep the text for this one, so we will add the icon in the :before pseudo class. The HTML looks like this :
<p> <a href="#"> Delete item </a> </p>
And the CSS code like this:
.delete:before{

content: " ";

display:block;

width:20px;

height:20px;

position:absolute;

left:6px;

background:url(img/delete.png) no-repeat;

}
Note that in this case, we gave the element both a width and a height but no padding. The next element to which we want to add an icon is the download button. The HTML looks like this:
<p> <a href="#"> Download </a></p>
And the CSS like this:
.download {

background:rgb(222, 227, 232) url(img/nuage.png) no-repeat 8px 6px;

border:1px solid rgb(199, 206, 212);

padding: 25px 0 25px 120px;

font-size:20px;

color:rgb(144, 160, 176);

text-shadow: 0 1px 1px rgb(239, 242, 245);

}
This is what we will call the third technique: assigning some padding, but no height or width. You will understand why below. For the footer however, we also assign a width and height for the element, padding too. The HTML:
<a class="bubble button" href="#"> bubble </a>
The CSS:
.ui-footer .button{

background-color:rgba(187, 185, 185, 0.2);

border:1px solid rgb(22, 22, 22);

box-shadow: 0px 1px 2px rgba(22, 22, 22, 0.5) inset ;

text-indent:-9999px;

padding:10px 15px;

width:40px;

height:40px;

background-position: center center;

background-repeat:no-repeat;

margin: 0 5px;

}

.bubble{

background-image:url(img/bubble.png);

}
At this point we have different case scenarios for the image replacement that will load non retina images for all devices, for now.

Media Queries Pixel-Ratio to the Rescue

The next idea was then to find a solution to make those devices load better quality images. I remembered the media query device-pixel-ratio (vendor prefix needed). I never used it before, and decided to give it a try. You will need some vendor prefixes here (Mozilla is the strangest one). The idea was pretty simple: I decided to try to serve those devices an image that would have twice the size of the desktop one. I chose a @2x notation for the retina image because I’m used to doing so when I create images for native iOS apps. I ended up doing something like this:
@media only screen and (-webkit-min-device-pixel-ratio: 2),

only screen and (min--moz-device-pixel-ratio: 2),

only screen and (-o-min-device-pixel-ratio: 2/1),

only screen and (min-device-pixel-ratio: 2) {

#myelement{

background:url([email protected]) no-repeat;

}

}
You would think that this works good. True, the retina image is loaded, but the problem is that the image is now twice the size. Still not displaying properly. Here is what it looked like on my Galaxy: the icons are nice and sharp, but not quite right. Android double sized

Background-Size Property Lends a Hand

Now that we have the high resolution images loading, we need to ensure they are the right size. To do this, we will use the super useful CSS3 background-size property that is actually able to resize backgrounds as needed. You can either use pixel properties for width first then height, use percentages, or set the value to “auto”. It’s simple to see it in the code. (Note that I used the id #retina for the demo purpose to only target the second part of the demo, but you can of course omit it in your code) For the header button you remember that we did set the height but not the width, to do the trick here, we will then set the background height to the same value (we can leave the width at auto).
#retina .ui-header h1{

background:url(img/[email protected]) no-repeat center center;

-webkit-background-size: auto 33px ;

-moz-background-size: auto 33px ;

background-size: auto 33px ;

}
For the delete button technique it’s a bit easier, since we did set both width and height AND since it has no padding, we can set the value to 100% for each, meaning that the icon will use the whole container space:
#retina .delete:before{

background:url(img/[email protected]) no-repeat;

-webkit-background-size: 100%  100% ;

-moz-background-size: 100%  100% ;

background-size: 100%  100% ;

}
For the download button, it gets trickier. Since we did not give it any width or height, we will then have to set the exact sizes of the non retina image for this one:
#retina .download {

background:rgb(222, 227, 232) url(img/[email protected]) no-repeat 8px 6px;

-webkit-background-size: 70px 68px ;

-moz-background-size: 70px 68px ;

background-size: 70px 68px ;

}
For the footer icons, we did set width and height, but the element has some padding. So here we will have to set at least one of the two values to make it work:
#retina .bubble{

background-image:url(img/[email protected]);

}

#retina .loupe{

background-image:url(img/[email protected]);

}

#retina .folder{

background-image:url(img/[email protected]);

}

#retina .ui-footer .button{

-webkit-background-size: 40px auto ;

-moz-background-size: 40px auto ;

background-size: 40px auto ;

}

And this is what it now looks like: Final product

What About HTML Images?

I only base this article on the CSS images, but of course there are also images directly in the HTML. For this, you will have to take a look at some responsive image techniques. So far I tested retina.js and have to admit that it’s pretty simple to use, you just have to put a @2x image in the same folder as the normal one and include the script. There is also the Retina Images plugin that seems to do the same job, but needs more server side configuration.

Limitations and Conclusion

As you can see, each case is different and you will have to play with the background-size values to get exactly what you want. The other limitation would be browsers downloading two images for this hack: first the normal, then the retina. I’m not an expert in this particular domain and did not run tests for the demo so if you want to, feel free to do and you can post the results I’m curious to know the browser used and if the images are downloaded twice. The techniques used in this article are based on a lot of CSS3 code, so might not be supported by all browsers. Also, having to create all the images in two sizes can be hard for maintaining the code, and take more space on the server side. So you will have to think carefully before you use such techniques. Forcing devices to load images twice the size, and then to resize them can also be bandwidth consuming. In conclusion, I would advise that even though this is a good technique for creating sleek pixel perfect nice interface for devices that support it, there are considerations to be made before using such a technique. Naturally, this won't be the solution for everyone.

Going further

If you are interested in displaying nice icons without having to create the files twice, you also can take a look at the iconic font technique and at SVG images. (Credits for the monochromatic icon set) (rb)

Stephanie Walter

Stéphanie Walter is a Graphic and Web Designer who loves is a Pixels and CSS. She enjoys working on UI and UX design for Mobile and Web Apps. She considers CSS as a design tool to create great interactive websites and likes to share her daily knowledge and experiment with CSS3

13 comments

  1. I found your guide very useful this morning, especially in knowing about the background-size neccesity as well as the media queries. Thank you.

  2. I use one image at twice the size of the image as I want it displayed, with a jpeg compression of 45. Then I set the width of it to 1/2 the size of the image (that is I start with a 1800 pixel wide image, and display it at 900px wide, with a height of auto). The compression is very low (my 1800 px image is 135kb) but not so low as to create artifacts in the image when resized.

    Tested this on Android, iPhone 5, tablets, and screens…the image looks very good.

    By the way, if you use a media query to display a different image, you have to download two images, eating up bandwidth.

Leave a Reply

Your email address will not be published. Required fields are marked *