(Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with all of them.)

The new iPhone 4 is certainly a marvel of technology.  One of the surprising side effects it has is how bad it makes things look that weren’t designed for high-DPI displays.  For the most part, text is automatically beautiful on the iPhone 4 1.  However, images are a different story. What follows is my research and then the technique I used to update images on flowz.com. We use wordpress here and with this technique, image replacement is automatic. All you have to do different is upload the second, high-res file at the same time you insert the normal file in your post or page. The first thing we need to do is pull in CSS specific to high-DPI device(s) in a standards compliant way. Here is how you can do that: http://thomasmaier.me/2010/06/css-for-iphone-4-retina-display/ The trick being to use a CSS3 media query to target high DPI devices (*not* just the iPhone 4!):

<link rel="stylesheet" media="only screen and (-webkit-min-device-pixel-ratio: 2)"
	type="text/css" href="css/highdpi.css" />

But that is only 1/3rd the story.  How do you make sure high resolution images are used when viewing with a High-DPI display?  Aral Balkan has the answer: http://aralbalkan.com/3331 But what he illustrates only works for “sprites.”  That is, divs and other elements that use background-images where the src is specified in CSS. In that special CSS file, we specify the size of the background and include high-res background images that override the normal CSS. However, there is the problem of elements that specify their src in the HTML. I.e., img, video, etc. Aral, touches on what is required but leaves the implementation as an exercise.  I’ll dive into the details:

  1. Create double resolution copies of all your images. Name these with a suffix of “@2x”. So for logo.png, the double res would be logo@2x.png. Make sure that all of your image tags have at least a width attribute specified. (But, you are already doing that aren’t you?) If you don’t, your image dimensions will be increased to the new high-DPI source by the browser.
  2. Create a highdpi.css file.  Contents:
    .replace-2x {
    	font-size: 1px;
    }

    The sole class, .replace-2x contains an attribute that has no effect on img tags. This is the flag that will tell our javascript it is okay to do image replacement.

  3. In your source HTML, add the replace-2x class to all of your images that have a double resolution counterpart.
  4. In your HTML head, add the link for your high-dpi stylesheet:
    <link rel="stylesheet" media="only screen and (-webkit-min-device-pixel-ratio: 2)"
    	href="/css/highdpi.css"/>
  5. Create and include in your source, a highdpi.js file.  We are going to assume you are using jQuery for your site.  Contents:
    function highdpi_init() {
    	if(jQuery('.replace-2x').css('font-size') == "1px") {
    		var els = jQuery("img.replace-2x").get();
    		for(var i = 0; i < els.length; i++) {
    			var src = els[i].src
    			src = src.replace(".png", "@2x.png");
    			els[i].src = src;
    		}
    	}
    }
    jQuery(document).ready(function() {
    	highdpi_init();
    });

The trick here is that we check to see if the font size for our special class is set to value declared in our high-DPI CSS file. (Our flag) It will only be set if the browser passed the media query. If the flag is set, grab every element in the DOM with the replace-2x class and change the src attribute to point to our high-res counterparts. Take a look at the images below (zoom in if it isn't immediately apparent to you):

Image without ".replace-2x"

Image with ".replace-2x"

(Under normal DPI monitors, the images will look identical.)

  1. Note: all text looks good, except text rendered using Cufón and sIFR. We hit that problem. A blog post will follow on that.

Sidebar

I really, really wanted to be able to do something different then what I described above. I wanted to define a class: .highdpi-supported on the body tag of all the pages that support high-dpi. I then wanted to add an uncommon attribute on that class to act as the flag to tell javascript to go ahead with image replacement. Unfortunately, I could not find any such attribute that was relatively safe *and* didn't affect page layout. So in the end, I'm stuck with adjusting the font-size attribute on the .replace-2x class.  Maybe someone else can come up with a better flag.

About the Author:

Learned something? Need help on your dev project? Well, I'm available for hire on project work:

  • Front End Development (jQuery, ExtJS, Backbone)
  • iOS Development
  • Server Side Development (Java Stack or Rails Stack)
  • System Architecture

Contact me:

Discussion

  1. Marcus says:

    Thanks for the very useful article Mike.

    I noticed that one of the snippets of code you’ve posted doesn’t work, simply due to the missing -webkit- in front of min-device-pixel-ratio within the css3 media query.

    Once I added -webkit- to this line, it all worked great.

    Thanks again.

  2. Mark says:

    Mike,

    Great – thanks for this, it’s helped me get my code up to snuff for Retina on the iPhone. The only issue I had with your code is that you assume I use jQuery. On a webserver, you’re absolutely correct, I would – but this same code is relevant for internal HTML files on the iPhone itself.

    One can load jQuery on the iPhone, I’ve done it – but on a native app it lags 1-2 seconds while loading, which is jarring to the user and I’d rather avoid it.

    I suppose it’s possible to do this w/o jQuery but I am not a Javascript master, so…

    Anyway, thanks!

  3. Jarrod says:

    How are you handling .jpg and .gif using this technique? Or is there a better solution now (a year later).

  4. mike says:

    Mark,

    I’d imagine the easiest thing to do would be to look at the line:

    src = src.replace(“.png”, “@2x.png”);

    and insert right after it:

    src = src.replace(“.jpg”, “@2x.jpg”);
    src = src.replace(“.gif”, “@2x.gif”);

    That way, all three formats are covered and you don’t care which file format is being used.

    • Sebastien says:

      Well, this is an old post and comment but I will share my replace anyways which works for me, using regex:

      src = src.replace(/\.(png|PNG|jpg|JPG|gif|GIF|jpeg|JPEG)$/, “@2x.$1″);

      Also It will cover cases (very unlikely, I agree) where the image has a .jpg or .gif in the middle, like “christmas.gift.jpg”

      Thanks for the very helpful post.

  5. Amy says:

    You are my hero for posting this. Thanks so much.

  6. Really useful, Mike!
    Thanks a lot :)

  7. Chris says:

    I’m currently implementing this method, but noticed that it causes the browser to make 2 requests for each image. First, it requests the normal image, and then if the JS recognizes we’re on a high-res device, it changes the img src, and a second request happens–the request for the @2x image.

    You can use the net tab in Firebug to see this happening. Is there a recommended way to avoid this? I imagine that loading twice as many images will have a significant effect on a mobile site’s performance.

    • mike says:

      Chris,

      This method is a trade off between performance and maintainability/ease-of-use. You certainly could create a site that only makes one request to the server for each image. The way to do that would be to serve up the HTML with src’s pointing to @2x files the first time around. However, that would require either your server-side application to support that, specifically, or you to maintain two separate source files.

  8. Niki says:

    thanks so much for this. I am using jqtouch and was struggling BIG TIME with the fact that their icons were super pixelated. I have looked on countless sites before I came across yours. Your fix it brilliant!
    Thanks again!

  9. Maciej Swic says:

    Not working on http://staging.appulize.com any ideas please?

    Thanks
    Maciej

  10. Amy Stoddard says:

    What I don’t understand is that the JavaScript to replace the images is being called regardless of whether “retina.css” is loaded. What can I add to the jQuery function call to see if the retina.css even go loaded?

  11. Amy Stoddard says:

    I found the answer to my own question. Detect whether its a retina display with Javascript. http://briancray.com/2011/05/05/detect-retina-displays-with-javascript/

  12. mike says:

    @amy:

    The function, highdpi_init(), is being called no matter what. However, the code that actually does the image replacement is only executed if the retina.css file is loaded. This line here:

    if(jQuery(‘.replace-2x’).css(‘font-size’) == “1px”) {

    Will only evaluate to true if retina.css was included.

    HTH.

  13. Ash Connell says:

    Would it not be better to run the @2x replacement script before the document is ready in order to stop loading the original sized image?

  14. Tim Bannister says:

    With XMLHTTPRequest, the client-side code could actually do a lazy (asynchronous) check for the high-resolution replacement images, and update the page whereever it finds one. It’s a lot more coding though, and wouldn’t suit high traffic websites.

  15. Madhava Jay says:

    I successfully implemented this method on this site: http://www.mynextwine.com.au, Mobile website only displays on mobile devices.

    The trick was to not actually have ANY images load by setting the img src to empty and having another param called url and put the original src value in there. Then I gave them the class retina if they had retina replacements and used this JS:

    $(‘.retina’).each(function() {
    var el = $(this);
    var src = el.attr(‘url’);
    if (el.css(‘font-size’) == ’1px’) {
    src = src.replace(‘.png’, ‘@2x.png’);
    }
    el.attr(‘src’, src);
    });

    The result is it only fetches the correct image and surprisingly it happens quickly.

    Also to avoid using jQuery on mobile I used Zepto.js which still gives me the $.jQuery style selectors.

    Its snappy and works great. :)
    The only trick is to make sure you put in those original non retina widths in your img tag.

  16. Brain Cray wrote this little snippet of code to detect retina displays.
    var retina = window.devicePixelRatio > 1 ? true : false;

    http://briancray.com/2011/05/05/detect-retina-displays-with-javascript/

    I’ve pieced that together with the code your wrote below:

    function highdpi_init() {
    // Feature detect for hi-res devices
    var hiRes = window.devicePixelRatio > 1 ? true : false;
    // Replace imgs with hi-res version .hi-res class is detected
    if (hiRes) {
    var els = jQuery(“.hi-res”).get();
    for(var i = 0; i < els.length; i++) {
    var src = els[i].src
    //alert(src);
    src = src.replace(".jpg", "@2x.jpg");
    src = src.replace(".png", "@2x.png");
    src = src.replace(".gif", "@2x.gif");
    els[i].src = src;
    }
    }
    }

    $(document).ready(function() {
    highdpi_init();
    });

    This seems to work on the iPhone 4S, will test on the new iPad tomorrow.

  17. Grappa says:

    The 2 Blue Images above looks on my iPad (3) absolutly identic.
    No, my eyes are ok :-)

  18. Leon Smith says:

    Excellent article Mike,

    I took the same approach when i was trying to figure out how to implement retina support on hi DPI devices but bundled it all into a jQuery plugin.

    https://github.com/leonsmith/retina-replace-js

  19. Oliver Clevont says:

    I found that the script is slow on my computer, but if I do:

    jQuery(“img.replace-2x”)

    instead of

    jQuery(“.replace-2x”)

    it will run faster.

  20. I find it bizarre you omit 1.5x devices which is mostly used dpi in Android devices. So to make your script work would I need to add .replace15x class to every element that has 1.5 resolution and write a similar function, say, wvga_init() ?

    • mike says:

      Would you suggest that we have a set of images for every DPI level? 3 sets now? Maybe even 4?

      I say no. Send the 2x images down to 1.5 devices. All you need to changes is: -webkit-min-device-pixel-ratio: 1.5

  21. There’s much better way of detecting pixel ration without resorting to ‘font-size:1px’ or even loading retina.css . Just add

    function getDevicePixelRatio() {
    if (window.devicePixelRatio === undefined) return 1; // No pixel ratio available. Assume 1:1.
    //console.log(‘get pixel called:’ + window.devicePixelRatio);
    return window.devicePixelRatio;
    }

    and then switch accordingly to the result: 0.75, 1, 1.5, 2. And call respective functions.

    • mike says:

      Sergi,

      Thanks for this!

      But to be clear, there are two reasons for the font-size:1px. One is to determine if high dpi artwork needs to be loaded. The other is to tag the images that have @2x corresponding images. Only images that are tagged with the class are swapped out.

      With your method, we could do away with the first. But we still need to handle images on a case by case basis.

      • Matt Boynes says:

        Sergi’s comment would still work. You could first do a check of devicePixelRatio and if it’s < 2 remove the .replace-2x class from the images. Then you could loop through elements with that class and replace the images as you've detailed.

        I also wanted to note that there's https://developer.mozilla.org/en/DOM/window.matchMedia which allows you the ability to use media queries in your javascript.

        Thanks for the article, it's very helpful!

  22. I’m on a Retina Display MacBook Pro and the images look identical.

    • mike says:

      Thanks for the note, Stephen. There was a problem with the site and the javascript wasn’t getting called. I’ve fixed it.

  23. Matt says:

    Thanks for this post. I’m new to HTML5/CSS3 so this may be an easy fix. I follow the steps below but can’t seem to get the image to adjust. What am I missing? Thanks!

    1. Made 2 logos and set width

    2. I made: highdpi.css

    3. put class=”replace-2x” in header.php where logo tag is.

    <a href="” id=”logo” class=”replace-2x” title=”">

    4. Placed this in index.php.

    5. made .js

    What am I missing? The logo isn’t being replaced.

    • mike says:

      Verify a couple things using web inspector/firebug:

      1. Is the .js file being included?
      2. Is the highdpi.css file being included on retina device? When you can’t inspect the device, I include an extra style block to debug. Something like:

      body {
      background-color: red;
      }

      3. If both are being included, you can add console.log statements to the init_highdpi() to make sure the images are being swapped out. The jquery lookup may not be finding your img tag.

  24. Thank you for every other great article. Where else could anyone get that kind
    of information in such a perfect means of writing? I have a presentation subsequent week, and I’m on the look for such information.

  25. Felicitas says:

    The amalgamation of reasoning, sequence solving, pattern recognition, logic, strategy and word completion will
    put you straight to the main stream of the game.
    At present, it has become important for businesses to
    have their own apps and mobile websites. keeps on changing the recent boom in mobile gaming sector seems to be the best
    alternative in this chaotic and stressful life.

  26. Google says:

    is updated frequently with free advice about Google Ad –
    Words strategy, tactics, tips tricks and techniques for success in Ad – Words advertising.
    * Let you know there are things you can do to improve y0ur ranking.

    Reputation Defense Online an around the world Cyber Investigation along with Litigation Assistance Agency for Net Defamation, often receives inquiries
    from attorneys along with law enforcement agencies on the way to subpoena Google’s Legal Division.

  27. Sooner or later, Google will find all new spam methods.
    In addition, the observing surgeons could transmit their comments to the operating surgeon, who could read them on the Google Glass monitor.
    * Page SEO: Your page is optimized by various means which include, choosing the right keywords, placing them right, adding anchor text linking,
    adding call to action in various places on the page.

  28. Gabriele says:

    Now participate in games like Tour de France and Extreme Motorbike,
    and practical experience your moment of virtual fame.

  29. Hey there! I know this is somewhat off topic but I was wondering if
    you knew where I could find a captcha plugin for my comment form?
    I’m using the same blog platform as yours and I’m having trouble finding one?
    Thanks a lot!

  30. malvinshops says:

    I read this post completely regarding the difference of hottest
    and earlier technologies, it’s awesome article.

  31. It’s amazing in favor of me to have a website, which
    is valuable in support of my know-how. thanks admin

  32. If you are going for most excellent contents like me, just
    pay a quick visit this web site every day for the reason that it presents quality contents,
    thanks

  33. Terrance says:

    I do agree with all of the ideas you’ve introduced for your post.

    They are really convincing and will certainly work.
    Nonetheless, the posts are very brief for newbies. May
    you please lengthen them a little from subsequent time?
    Thank you for the post.

  1. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  2. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  3. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  4. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  5. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  6. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  7. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  8. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  9. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  10. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

  11. [... (Note: This article was originally written for the iPhone 4. Since then, we have seen the iPad 3 with Retina, and now the MacBook Pro. This technique works with ...]

Leave a Comment