Grow your CSS skills. Land your dream job.

Techniques for Context Specific Images

Published by Chris Coyier

One of the shortfalls of using CSS media queries as the only ingredient of a mobile solution is that the same content gets served to both desktop browsers and mobile devices (which theoretically are slower and have less network speed).

Serving the same HTML to both... not as big of a deal. Serving up images that are many times bigger than they need to be on mobile, that's more of a problem. A number of smart people have tried to solve this problem, so I just wanted to highlight their work here:

The CSS3 Way

While this one by Nicolas Gallagher currently doesn't work in any browser, I think it's my favorite. You put the smallest of the images you have as the default source, then list the URL paths to larger versions as HTML5 data attributes.

<img src="image-mobile.jpg"
     data-src-desktop="image-desktop.jpg"
     alt="">

Then you use CSS generated content to replace the graphic with the larger one, via media queries, if the screen width indicates it's a desktop.

@media (min-width: 1024px) {
    img[data-src-desktop] {
        content: attr(data-src-desktop, url);
    }
}

Even the current dev version of the CSS spec, which has been trimmed way down from the original, retains this ability.

Nicolas clearly outlines the potential downsides of this in his article, I feel like each of them is addressable with future technology.

The JavaScript Way

While the above CSS3 way is theoretical at this time, this JavaScript technique by Scott Jehl of the Filament Group is ready to use (if still experimental).

This technique is actually a combination of JavaScript, HTML, and .htaccess (so Apache servers only). The readme file explains it best:

As soon as rwd-images.js loads, it tests the screen width, and if it's large, it inserts a BASE element into the head of your page, directing all subsequent image, script, and stylesheet requests through a fictitious directory called "/rwd-router/". As these requests reach the server, the .htaccess file determines whether the request is a responsive image or not (does it have a ?full query parameter?). It redirects responsive image requests immediately to their full size, while all non-responsive-image requests go to their proper destination through a URL rewrite that ignores the "/rwd-router/" segment.

The Cookie Way

Keith Clark has a method of storing a cookie which contains the screen size of the current device. That cookie can be tested before images are served and used to decide what size to serve.

It works by setting a cookie of the screen dimensions.

document.cookie = "device_dimensions=" + screen.width + "x" + screen.height; 

Then images are actually routed through a simple script. The file name is a parameter.

<img src="images/?test.png">

The script reads the cookie and serves up the appropriate image. See the full article for all the code.

Any others?

Have you see any other projects that have attempted to tackle this problem? I'd love to keep this article up to date with the best methods.

Comments

  1. I have been playing around with this mobile boilerplate that loads the smaller things first then adds the larger versions as the screen size gets bigger. I guess this is a little different than what you are talking about, but thought I’d mention it anyway.

    http://stuffandnonsense.co.uk/projects/320andup/

    Love your site by the way. It’s helped me countless times!

  2. If, hypothetically, all your image assets are being loaded through CSS via background-image (I realize this isn’t realistic, just thinking out loud here), then could you just make it a point to put media queries around anything that involved images? That is to say, if you’re on a desktop, first test to make sure your resolution is large enough and then serve up the large image? I assume if you did that, the mobile versions would not try and load the larger, desktop version, correct?

    And with regard to elements, if you by default have a rule that says

    img { display none; }

    … And then used media queries to selectively set to display to inline/block/whatever if you meet a certain resolution requirement, would smaller screen devices still actually load image asset even though display is set to none?

    • There are media queries that allow for checking the screen width and whatnot. Somewhere on the infinite resource that is CSS-Tricks, Chris actually covered this.

    • Unfortunately your assumption is incorrect many (most?) mobile browsers will download all of the images they find in a style sheet regardless, even if a media query means they will never be used

    • @Martin Ansty: Would a mobile browser download the image if it was on a class that never gets used? Was thinking that if you detected the browser and then added a class to the body you could then specify specific images for specific browsers… therefore unused classes wouldn’t load.

    • Just run a couple of tests in Firefox with the FireBug add-on. Found that images on unused classes weren’t being loaded. If you want to check for yourself its in the Net > Images section… shows you all the images that get loaded.

    • HughJohnson

      I do the exact way you suggest with all images being backgrounds and media queries to change images. I use the same technique, with background-size to show @2x images on iPhone4. My server logs don’t reflect that most mobile browsers will download all the images, it seems to work great.

  3. Rikudo Sennin
    Permalink to comment#

    Loving the PHP way :-D

  4. Bonde
    Permalink to comment#

    What about Sencha.io Src (Used to be Tinysrc)?

    http://www.sencha.com/products/io/

    I’ve been using this on a few sites and it works pretty well for me.

  5. Permalink to comment#

    Even if you use media queries ALL assets specified in your CSS will get loaded. I wrote about this today http://wolfslittlestore.be/2011/06/28/load-times-responsive-images/

    Setting each image as a background image is not realistic in a context where the content is input by a client through a WYSIWYG editor like FCK or TinyMCE.

    And even if it was possible it would be a really bad idea since it breaks the semantics of the web. Google images won’t index your background images.

  6. I believe that you could also put all of your background-image declarations in two separate stylesheets and use media queries that way to determine which image to load initially:

    Or am I missing something with this route (aside from loading two sets of images if you were to resize your browser)?

    • Gargh! I really wish you could edit comments:

      <link rel=”stylesheet” media=”screen and (max-width: 800px)” href=”small_graphics.css”>
      <link rel=”stylesheet” media=”screen and (min-width: 801px)” href=”normal_graphics.css”>

    • Of course, this would only work for background images, not actual img tags!

  7. Joe
    Permalink to comment#

    This is a critical subject. This affects not only mobile graphics but sites that use large detailed graphics. I prefer to load low quality images first so the site loads its full content faster then apply high quality images after the page has loaded. We used to be able to use lowsrc for this, but using a new technique that not only detect your screen size but your network speed would be very useful.

    • Clayton
      Permalink to comment#

      What sort of method do you use to go from low res to high res?

    • Joe
      Permalink to comment#

      This examples changes the html background after the page has loaded but it could be adapted for inline images and without using backgrounds, such as using an alternative img attribute:

      $(window).load(function(){
      $(“html”).addClass(“bgHighRes”)
      })

  8. Gary Pollard
    Permalink to comment#

    I need some help using PS CS5.

  9. I used a bit of jQuery combined with timthumb.php for a friend’s homepage. The script looks like this:

    var ssWidth = 0;
    var ssSrc;
    var ssImg;
    var ssc;
    jQuery(document).ready(function(){
        ssc = jQuery('#homepage-slideshow');
        ssImg = ssc.children('img').first();
        ssSrc = ssImg.attr('src');
        genImage();
        jQuery(window).resize(function(){
          if(ssc.width()!=ssWidth){
            genImage();
          }
        });
    });
    function genImage(){
        ssImg.hide();
        ssWidth = ssc.width();
        ssImg.attr('src','/util/timthumb.php?src='+ssSrc+'&w='+ssc.width()+'&h='+ssc.height());
        ssImg.show();
    }

    This was so that he could change the homepage image by setting the WordPress featured image for the page.

  10. Pop dump
    Permalink to comment#

    I am new to CSS and this website on a whole but after reading the techniques and the comments I think I have come up with an algorithm to fix that issue…

    set mobile css
    check screensize
    if (screen size == desktop) then change css to desktop css

    put that into the portion of the website

    <link id="page_css" href="mobile.css" type="text/css" rel="stylesheet">
    <script>
    if (screen.width >= 800)
      document.getElementById('page_css').href = 'desktop.css';
    </script>

    simple code i think that should work well by setting the proper css for mobile/desktop….I havent tested it yet cause I am at work, will do so and check back when I get home later

    • Clayton
      Permalink to comment#

      That seems pretty interesting.

      Does anyone have negative or positive feedback for it?

    • Clayton
      Permalink to comment#

      Kevin Sweeny’s code seems to do the same thing, only as a standard practice.

      Am I confused?

      If I’m not, then it still doesn’t solve the problem of image tags in the markup, which is what this post is about.

  11. Permalink to comment#

    I ran into this same problem a little while ago, and came up with what I think is a good solution. Though I know and love PHP, it costs me more to host dynamic sites. So I needed to load the smaller image using only javascript. When the page is ready (but the images haven’t yet loaded) I simply edit the src of all the images on the page. I put all of my smaller res images in a folder /m and keep the same folder structure as /media, where all my desktop images reside. That way I can just replace ‘media/’ with ‘m/’ in the src. Here’s my code:

    $(document).ready(function() {
    // replace images here:
    if(480 >= window.innerWidth) {
    $(“img”).attr(‘src’,function(i, val) {
    return val.replace(‘media’,’m’);
    });
    }
    });

  12. Matt
    Permalink to comment#

    I’d like to point out a common mistake of javascript solutions. It’s not the screen size (screen.width and screen.height) we’re interested in, but the viewport size (document.documentElement.clientHeight and document.documentElement.clientWidth). While this might be the same for mobile browsers, it definitely isn’t for desktop browsers. Why should a dektop browser that runs at 600×400 pixels on a 1920×1080 screen get the same images as one that runs fullscreen at 1920×1080?

    Wasn’t there an article somewhere about this some days ago? http://css-tricks.com/9778-screen-resolution-notequalto-browser-window/

  13. What about giving all elements a data-largeimage src and using that with Javascript to replace the src=”” attribute when the viewport is bigger than e.g. 1024px.

    The original src=”” on the img tag is a mobile version of the image but on desktop it will be instantly replaced by the bigger image src that is provided in data-largeimage=”urltobiggerimage” because of the javascript that detected a desktop resolution.

    Any thoughts? (I’ll post an example later)

  14. Permalink to comment#

    How about this…

    Context Specific Images

    * { margin: 0; padding: 0; }
    img{ width:100% }
    body { background: #CCC; }
    #page-wrap { width: 256px; margin: 0 auto; }

    @media (max-width:769px) {
    #page-wrap { width: 150px; margin: 0 auto; }
    }

    var winW;
    var img;
    var imgState;

    function specificImage() {
    img = document.getElementsByTagName(“img”);
    winW = window.innerWidth;

    if (winW >= 770 && imgState != “desktop”) {
    imgState = “desktop”;
    for ( var t = 0; t < img.length; ++t ) {
    var imageChanger=img[t].src;
    img[t].src = imageChanger.replace("mobile","desktop");
    };
    }
    else if (winW <= 770 && imgState == "desktop") {
    imgState = "mobile";
    for ( var t = 0; t < img.length; ++t ) {
    var imageChanger=img[t].src;
    img[t].src = imageChanger.replace("desktop","mobile");
    };
    };
    };

    window.onresize = function () {
    specificImage();
    };

    All you need is two sub-folders in your images folder, one with the large images and the other with the small images. The page will load the small images by default, then detect the window size. If the window is bigger than 769px (in this case) it will load the bigger images. It will also swap the images when resized.

    • Permalink to comment#

      Oops! forgot to change the code…

      <!doctype html>
      <html lang="en">
       
      <head>
      	<meta charset="utf-8">
          
      	<title>Context Specific Images</title>
      
      	<style type="text/css">
              * { margin: 0; padding: 0; }
              img{ width:100% }
              body { background: #CCC; }
              #page-wrap { width: 256px; margin: 0 auto; }
          
              @media (max-width:769px) {
                  #page-wrap { width: 150px; margin: 0 auto; }
              }
          </style>
      </head>
      
      <body onLoad="specificImage()">
          <div id="page-wrap">
              <img src="images/mobile/logo.png" alt="" border="0">
              <img src="images/mobile/logo.png" alt="" border="0">
              <img src="images/mobile/logo.png" alt="" border="0">
          </div>
          
          <script>
              var winW;
      		var img;
              var imgState;
      				
      		function specificImage() {
      			img = document.getElementsByTagName("img");
      			winW = window.innerWidth;
      			
      			if (winW >= 770 && imgState != "desktop") {
      				imgState = "desktop";
      				for ( var t = 0; t < img.length; ++t ) {
      					var imageChanger=img[t].src;
      					img[t].src = imageChanger.replace("mobile","desktop");
      				};
      			}
      			else if (winW <= 770 && imgState == "desktop") {
      				imgState = "mobile";
      				for ( var t = 0; t < img.length; ++t ) {
      					var imageChanger=img[t].src;
      					img[t].src = imageChanger.replace("desktop","mobile");
      				};
      			};
      		};
      		
      		window.onresize = function () {
      			specificImage();
      		};
          </script>
      </body>
      
      </html>
  15. Brian Hough
    Permalink to comment#

    You could probably adapt the techniques used in the Retina Display jQuery Plugin: http://troymcilvena.com/post/998277515/jquery-retina

    It appends a -2x to a file name to use the higher resolution version when it detects a high DPI display. You have to set the height and width to keep it from being twice the size obviously.

    You could do the same thing just detecting screen/window width instead. Replace the images with the larger versions when it detects a larger width. Without the h & w set, it will be whatever size you set it for. Shouldn’t take much to make it do this on the fly when the window size changes.

  16. I think it is less harmful – when possible, of course – just to resize images to fit smaller screens. The image downloads will be larger, but we still got the possibility to do media queries to change the website layout on-the-fly (when resizing browser window) and don’t rely on mobile user-agent detection.

    • Easier said than done in my opinion. While many mobile users these days do have 3G or better, I’m not sure if it’s exactly ubiquitous. Regardless, forcing larger images on the user seems wrong. Not only would you be ignoring their inherently slower connection, but you also aren’t respecting the fact that many users don’t have unlimited data plans and have to keep their downloads under a certain limit to avoid overage charges.

  17. Sounds like many people are getting confused between background images loaded via CSS and images in the HTML.

    This article is about images in the HTML.

    Josh. I haven’t tried your code but it’s about what I had in mind. Combine this with a script that automatically creates both versions of the IMG when uploaded via a CMS and it’s a winner.

  18. Permalink to comment#

    Hi, nice article. Windows can also support URL rewriting: IIS7 has it built in and IIS6 can use a 3rd party plugin e.g. from HeliconTech. Unlikely to be present in shared hosting packages based on IIS6, but as hosts upgrade more of us can enjoy .htaccess type benefits!

  19. How can i use the css3 way in a cdn environment like on my site?

    http://www.benchee.com/

    I could choose every possible size of an image and have a lot of different subdomains for each image?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".