Treehouse

Truly Responsive Images with responsive-images.js

By on  

Responsive web design is something you hear a lot about these days. The moment I really started to get into responsive design was a few months ago when I started to realise that 'responsive' is not just about scaling your websites to the size of your visitors screen but much more than that.

Truly responsive web design is about completely adjusting your website to the visitor's device. It does not just include scaling, but just as important is reducing the data you transfer to visitors who are on a slower connection like 3G or even EDGE.

Image Replacement

One of the most data consuming elements on a website are the images. To reduce the data they give to our website we can replace them with more suitable images for the device our visitor is using.

This is something which is natively done using the HTML5 <picture> element W3C is developing. The element is not supported in any browsers yet and until it is we'll need either a back-end or a javascript solution to get this done.

Plugin?

There are a number of plugins for this already out there. However, when I searched for one I didn't really find one that suited my needs. Most of them required some kind of additional javascript to be imported next to the plugin and the HTML markup they use seemed a bit dirty and unorganized to me. I got the idea that there might be a cleaner solution possible.

responsive-images.js

This is when I came up with the idea for responsive-images.js. Which is a simple and lightweight javascript plugin(1kb) for responsive image replacement. It uses a clean HTML markup and does not require any additional javascript in order to work.

Usage

<img alt='kitten!' data-src-base='demo/images/' data-src='<480:smallest.jpg                                                                            ,<768:small.jpg,
                                                          <960:medium.jpg, 
                                                          >960:big.jpg' />

<noscript><img alt='kitten!' src='demo/images/medium.jpg' /></noscript>

That's an example of what the HTML looks like. Pretty clean right?

Using the markup above the browser loads demo/images/smallest.jpg if the size of the viewport is below(or equal to) 480 pixels, demo/images/small.jpg if the size of the viewport is above 480 pixels and below 768 pixels, demo/images/medium.jpg if the size of the viewport is above 768 pixels and below 960 pixels and demo/images/big.jpg if the size of the viewport is above 960 pixels.

Retina?

But what if my visitor is using a retina device?

<img alt='kitten!' data-src-base='demo/images/' data-src='<480:retina/smallest.jpg                                                                      ,<768:small.jpg,
                                                          <960:medium.jpg, 
                                                          >960:big.jpg' 

                                                data-src2x='<480:retina/smallest.jpg                                                                     ,<768:retina/small.jpg,
                                                            <960:retina/medium.jpg, 
                                                            >960:retina/big.jpg'/>

Tadaa! Using the data-src2x attribute it is possible to specify the images the script should use in case the device has a retina display.

Something that bothered me about this markup though, is that all of the image paths are defined twice. I usually would just save my retina images in a seperate subfolder like demo/images/retina. So to make the markup a bit cleaner there is also the option to only change the data-src-base path for retina devices using the data-src-base2x attribute.

<img alt='kitten!' data-src-base='demo/images/' data-src-base2x='demo/images/retina/' data-src='<480:smallest.jpg,
                       <768:small.jpg,
                       <960:medium.jpg, 
                       >960:big.jpg' />

No src attribute?

From what I can see, it looks like an <img> tag that requires JavaScript to generate a source, which I'd be a bit uncomfortable with. -- Tech Blogger

Yeah, I'd be a bit nervous about that as well.

Browsers are too quick for us! In order to provide the fastest loading time possible, browsers preload all of the images that they can identify -- Choosing A Responsive Image Solution, Smashing Magazine

The problem is though, as described by Smashing Magazine, that when you set a src attribute the browser will preload the image before any javascript or even CSS is applied. This would make double loading of images unavoidable.

I don't see any workaround here yet (thoughts, anyone?). The good part is that the plugin even works in browsers like IE5 and Opera Mobile and that there are no known unsupported browsers yet, which makes it pretty safe to leave out the src attribute.

Behind the scenes

How the plugin itself works is pretty easy. It loops through all the images on the page and first checks if the image contains a data-src attribute to determine if the image is supposed to be responsive

if( !image.hasAttribute('data-src') ){
    continue;
} 

Then it splits the data-src attribute at every comma which gives us something like:

[<480:smallest.jpg, <768:small.jpg, <960:medium.jpg, >960:big.jpg]

It starts looping through the results and splits every result again at the colon

[<768, smallest.jpg]

Now we determine if we are talking about above 768 or below 768 by simply calling an indexOf

if( query[0].indexOf('<') )

We split the string again at the angle bracket.

query[0].split('<') 

Now, before matching the 768 against our visitors viewport we first have to determine if their is any lower breakpoint available.

if( queriesList[(j -1)] ) 

In this case there is a lower breakpoint set of 480. Like above, we split the string at its square bracket and colon. Now we check if the viewport is between our two values.

viewport <= breakpoint && viewport > previous_breakpoint

If this is the case then we simply change the image source to the source that belongs to the 768 breakpoint

image.setAttribute('src', newSource); 

At this point the image source is set to demo/images/small.jpg and our visitor is viewing the right image for his/hers device.

Browser support

As soon as the plugin was functional I started doing some browser tests. I tested on iOS, android and various desktop browsers. Since I really wanted to see was how far I could raise the bar also older browsers were tested including IE5, Opera 10.6, Safari 4, Firefox 3 and Chrome 14. On the phones I tested devices including Android 1.4, iOS 3 and even Opera Mobile.

While doing these tests I ran into a few problems. Two were for IE but one was for our beloved iOS...

1. Viewports on mobile

Easy right? Just put document.documentElement.clientWidth in there and you're good. That's what I thought. But it appears to be a bit more complicated than that. Without a proper width=device-width set in your meta tag some mobile devices will return the viewport as being standard size(980px) which would cause the script to return an image suitable for a 980 pixels wide viewport.

I haven't been able to find a javascript only solution for this yet and am not sure if there is one. Taking the fact that most responsive websites have width=device-width in their meta tag anyway this is not a huge problem. I would however like to investigate this some more. If you have thoughts about this, let me know!

2. hasAttribute

To determine if an image has a data-src attribute the scripts makes use of the hasAttribute method. The problem with this however is that IE7 and below do not support this. For them I had to create a workaround.

We first check if the hasAttribute method is available

if( image.hasAttribute )

If it is we use the hasAttribute method

image.hasAttribute('data-src')

If it is not available we use a workaround

typeof image['data-src'] !== undefined

Those are the basics of this all. However, after this I bumped into another problem. I thought: IE7 and below do not support hasAttribute so why not just define the hasAttribute method myself in case it does not exist? A prototype function is out of the question since IE7 and below do not support them either so I created a normal one.

if( !images[0].hasAttribute ){

    function hasAttr(el, attrName){
        return typeof el[attrName] !== undefined ? 1 : 0;
    }

} else {

    function hasAttr(el, attrName){
        return el.hasAttribute(attrName) ? 1 : 0;
    }

}

Do you already see my mistake here? Declared functions are loaded before any of the other code is executed which makes our if statement invalid and results in a Object does not support property or method hasAttribute error. Let's try that again

if( !images[0].hasAttribute ){

    hasAttr = function(el, attrName){
        return typeof el[attrName] !== undefined ? 1 : 0;
    };

} else {

    hasAttr = function(el, attrName){
        return el.hasAttribute(attrName) ? 1 : 0;
    };

}

Now if we use function expressions, the code will only load when the interpreter reaches that line. Which makes our if statement work.

3. addEventListener

Next up is the addEventListener method which is not available available in IE8 and below. They instead use the attachEvent method. Just like for the hasAttribute method I also used a simple workaround here by first checking if the addEventListener method exists.

if( window.addEventListener ){

If so, use it!

window.addEventListener('load', makeImagesResponsive, false);
window.addEventListener('resize', makeImagesResponsive, false);

if not, use the attachEvent method

window.attachEvent('onload', makeImagesResponsive);
window.attachEvent('onresize', makeImagesResponsive);

What about you?

Make sure to check out the Github page. I'd feel honored if you'd like to fork and contribute and would let me know what you think. I'd love to hear your feedback! ;)

Excited?

Grab all the devices you can find and check out the demo below for some responsive kittens. :)

Koen Vendrik

About Koen Vendrik

Koen Vendrik is an interaction design student with a passion for web design and development and great user experiences who is currently based in Amsterdam, The Netherlands. He focusses on creating clean and friendly websites for all types of users. Check out his blog to stay up to date with what he's up to and check out his Github to contribute to his projects.

ydkjs-1.png

Recent Features

Incredible Demos

Discussion

  1. Man, I wish we could just do this without any javascript…

    I’ve made a plugin like this one as well, but mine can also be configured to react to the element’s width as well as the viewport’s width (because we all like element queries

  2. Oops, the emojii caused a bug in my comment… Here’s the address : https://github.com/etiennetalbot/responsImg

    • Etienne Talbot – Looks good! I had a few questions though.

      1. You require jQuery in order to use your in order for people to use your plugin which increases the payload for smaller websites who do not require any other jQuery functionality.

      2. Another thing is that you are creating a new attribute with ‘data-responsimg’ followed by the the pixel width or one of the default sizes for every breakpoint. This can get a bit messy since the attribute names are quite long. For something like four breakpoints you’ll end up with quite some code.

      3. Defining a 2x image is done by adding the filename in the same attribute which is nice but requires you to also redefine the path. Like when I would like to define a 480 pixels wide breakpoint. I would have to use something like:

      data-responsimg480=”assets/images/image-480.png, assets/images/image-480@2x.png”

      This get’s quite long if you have a long path to the image.

      4. You are using a `src` attribute which causes the browser to first load the image specified in there and then change it to the correct image for the viewport which causes double loading. I understand the SEO issue here that is why I’m still looking into this as well to find a solution in which double loading is not required.

      Let me know what you think. :)

  3. This looks like a really good solution. From what I read elsewhere yesterday it appears W3C may be moving away from the idea of and its associated syntax and towards an type of syntax and tag. The “src-n” fits right in with what you have in this plugin and its associated HTML so it should be very future friendly and easy to update as W3C adopts this responsive solution to image serving.

    • Hey, yeah, hope the solution W3C comes up with will we widely supported quickly after the release so the whole responsive images thing can be done natively. :)

  4. Strange effect in Chrome on Nexus 5 – 1st view of demo in portrait, I get ../retina/big.jpg (1240×775) and ‘Viewport 360′. On rotating to landscape it becomes retina/small.jpg (768×480) & viewport 598. Rotate back to portrait and I get retina/smallest.jpg (480×300) viewport 360.

    360×598 are the screen dimension the browser normally reports, although actual pixels are 1080×1900 (x3).

    • Hey David, Thanks for noticing that! I will make it an issue on the Github page and look into it as soon as possible.

  5. vic

    A nice improvement would be speed. Maybe detect download of the first image download and then if speed is ok, update to retina.

    • Hey vic! The internet speed of the user if very important! I am currently still looking into this to see if it would be possible to check what type of connection our visitor is using and determine what image to use from there.

      (Updates on this can be found on the Github page Github page.)

  6. MaxArt

    typeof returns a string, so the correct expression is
    if (image['data-src'] !== 'undefined') ...

    • Hi MaxArt! Haha had that before the getAttribute actually but it kept returning undefined in IE6. I will look into it further though, thanks for bringing it up! :)

  7. Hey nice and clean solution. Congrats.

    However, the way you register images is too weak.

    Waiting for window onload is not sufficient. On moat websites its about 4-5 seconds.

    Even registering on dom ready would not be sufficient.

    On most websites you will have dozen of inline/external JavaScripts that will dramatically slow down image loading. Because they register handlers on dom ready. Wondow onload.

    You might want to have a look at https://github.com/vvo/lazyload/blob/master/README.md#hidpi-images where I present a lazyloader with a nice custom src chooser based on a function.

    Basically you could plugin your data-src data-src2x using a simple cb using my lazyloader.

    You will benefit of automated testing and robustness.

    • Hey Vincent, yeah could be a good idea to change the on window load to something a bit earlier. Content should in most cases load before the images though but I’ll definitely look into it!

  8. hereandnow

    nice article.

    about the src-problem. why not use an 1×1 transparent png for all images on the page using this technology? the browser would only have to load 1 additional image, which size is small enough do do so.

    moreover i thought if it would be possible to detect width/height of the image to load in a reliable way? i dont think this is possible, but i havent tried much until now…

    • Thanks for your feedback!

      Using a transparent image would not help with the SEO and if the javascript not works on a page still show nothing, but I might be off here?

      Automatically matching the viewport with an appropriate image was not the first aim of the plugin but I’ll look into that! :)

  9. @david preston thanks for pointing that out I will look into it as soon as possible. :)

  10. @vincent voyer yeah could be a good idea to change the on window load to something a bit earlier. Content should in most cases load before the images though but I’ll definitely look into it!

  11. David Schooley

    Is it just me, or are you missing commas in your first 2 responsive-images.js examples…? In the first line of data-src and data-src2x attributes.

    This is neat. Still not ideal… Although I believe mobile browsers don’t load images that are hidden by CSS. Combining this with media queries may allow you to use an src attribute and still use your data-src attributes for responsiveness. Just an idea.

    David

    • Eugene

      David Schooley – just scroll it to the right :)

    • The thing with CSS is that some browsers seem to load a display: none; and some don’t which makes it unreliable. I will look into it though since there might be something possible by only using this solution for mobile if it would work on all platforms there. :)

  12. Mathias Valentin

    I have been using a similar approach for some time now. But I have added the attributes to the and then I’m replacing that element on image load. Even less html code.

    • Hi Mathias,

      Sounds interesting! Are you replacing the whole element? Would you mind to give a more detailed description of the method you’re using? :)

  13. Gustav

    Shouldn’t there be a comma after each attribute in the data-src property? looks like you forgot them a few times

    • They floated a bit to the right in the box haha. If you scroll to the right you can see them. :)

  14. Ah, responsive images… the joy! We all know we love them. Let’s be thankful for what we have, team up, and go from there in this wondrous world of the Interwebs :-)

    /* End rant, begin useful stuff */

    I use Media Queries and the background-image property. The browser will only load the images needed for the current viewport. Sure, when you re-size the browser, or turn your phone/tablet on it’s side, you’re essentially creating additional HTTP requests for additional images once you pass a breakpoint. I think that is acceptable — this way, we are streamlining content for the current device width, and when it changes, serve up some better-fitting content.

    Pro: the aforementioned. Plus, having control over the images at whatever custom breakpoints are needed to keep with the “design vision” of each image displayed.

    Con: lots o’ images. Doesn’t scale up nicely if dealing with a humungo CMS with tons of images.

    • Hey Josh,

      Especially enjoyed the rant part. ;)

      The method you’re using sounds good but I would however like to add some more cons.

      1. This requires you to specify all of your images in the CSS. You would specify the sources in two places which can be a problem for dynamic pages because the CSS needs to be modified server-side.

      2. On an image heavy website, you will end up with a large lists of media queries for all the images you have.

      3. Each individual image needs to get a class or id in order to be able to get specified by the CSS.

      The upside is that there is no javascript required in order for it to run. The downside however is that this in my opinion is a pretty dirty solution. What are your thoughts on this? :)

  15. This is a great tool! However, I noticed something. When I resize my screen, the size is checked and the image reloaded. However, doesn’t this mean downloading another image?

    • Hi Paul,

      Yes! when resizing your screen the script checks the new size of the window and downloads the appropriate image. It does this so when the visitor for example loads the page in a small window and then scales it to a bigger one the image won’t get blurry but nicely replaces itself with a bigger version.

      I agree with you that this is not perfect. A solution to this would be to only execute the script only on page load but then, as named above, the visitor might end up with blurry images when upscaling the window. What do you think about this? :)

    • Well, the only thing I could think of, is to only load the image when it’s upscaled.

      A while back I had built basically a PHP version of this and I had this issue as well. I found the best way is to have a margin of downloading. Basically, you would only download a new image if the new windows size it outside the threshold of the old one. This fixes downloading two images when tablets like the iPad mini switch their orientation.

      Another way which would also only fix half of the problem, is to only download new images on upscale (you might have actually done this, as I haven’t checked out much of the code).

      I do agree though that if they’re upscaling by a major factor then it’s probably safe to download a larger image, because they probably aren’t mobile.

    • I am currently still looking into this. Someone on the Github page had the suggestion of using a throttle so the function only executes every, for example, 250 milliseconds and not during the whole resize event. Someone else suggested to only run the function when the resize event is finished.

      Both solutions are not ideal but I will try to come up with a solid solution. if you have any idea’s let me know. Here, on Github or Twitter! :)

  16. Koen – we are a startup building a Web-based Responsive front-end builder that does not need any hand-coding and we believe Responsive is all about Usability and the User Journey (within an app/page and across form factors). there is a lot of chatter of Responsive damping SEO profiles etc but they are missing the point – these are two different issues and Responsiveness should have no influence on SEO per se (but perhaps only indirectly if users don’t visit your unresponsive page and then you get spidered down the list).

    You are aboslutely right to focus on the importance JS (incidentally, RapidMoon is also built using JS!)

    Ariel
    http://www.rapidmoon.com

    • Hi Ariel,

      Thanks for the reply! Good to hear you agree. :)

      What do you think of the fact that the plugin does not use a src attribute. This is something I am planning to look into as it might influence the SEO since Google not be able to collect the image.

      Using a src however is not a option since the browser would preload the image. Another option, as some people suggested, would be to use the smallest image as the native source but this would cause double image loading.

      What are your thoughts on this? Let me know. :)

  17. I was looking for like this in ages! Does the image need to be high in resolution for it to look good? I mean, like the cat? Thank you!

    • Hi Daphne,

      No you can use it in any resolution you want! Check out the Github page for some usage tips. :)

  18. Michael

    I just started tinkering with YARISH ( Yet Another Responsive Image Solution Hack ;) last week. Before I delved into all the discussion and hacks on this issue, I started down a path of my own and wanted to then compare to what has been proposed or implemented previously.

    I’m still catching up (a new client website will need responsive image support), but I have not see exactly the same markup as what I am doing so might be worth mentioning here.

    I decided to not use srcset or picture and instead remian focused on using the normal img src element and attribute with javascript and a rewrite rule for redirects.
    Basically, I chose to include multiple img src references as opposed to just one and they are either comma or space delimited with a predefined standard order based on smallest to largest (in filesize). Likewise, other attribute values can be added in same manner and correlate to the images referenced in src attribute. This is essentially how some CSS3 shorthand or bg layering is handled so I figured might as well pull it into the HTML markup for this hack.

    Since the browser detects these img src references as broken image links before the code replaces the value with the appropriate image based on responsive conditions, I decided to use an apache rewrite rule to redirect all broken images to a 54 byte 1×1 gif image. Alternatively, you could just let the browser handle 404 responses but that would actually be heavier. So the sacrifice is the 54 bytes for every responsive image handler in the markup. Not ideal but what hack is? If rewrite rules are out of the question, then the first image could be used and loaded but it would probably require extra markup and code which I am just not ready to explore yet.

    My next steps were to get into the details of handling the conditions and the markup related to naming conventions of attribs and/or filenames. I also am considering appending image file names with parameters in order to communicate commands and metadata associated with that image, as opposed to making more verbose HTML markup. This approach would be more akin to having a new file format with layers and metadata embedded but since we don’t have that atm we could just add loose params to the path/filename.jpg?like=this

    I think that instead of data-src-base I will just assume that either dynamic scripts or generated markup can intelligently handle image path inclusion so will skip that for now. Likewise, having multiple sets of data-src (data-src, data-src2x) is ok but I would rather define all of this within the assumed and accepted standard of shorthand ordering (like how we accept CSS margins as Top Right Bottom Left clockwise order). So in this case, the the 2x retina image set could be defined after all other images and either separate with a different character such as a pipe or group each set in brackets or maybe a semicolon in place of a comma denotes a new set for 2x.

    Instead of preceding the greater than less than signs in front of the media querys, I might prefer more accepted assumptions such as all undefined expresses everything up to that value end below but if a plus sign is added then everything at and above (>960 would just be 960+ and <960 would just be 960).

    Maybe my version of your example would look like this:

    I’m just feeling this out so know that I am aware of the dirtiness but like I said, I am borrowing from CSS Shorthand to accomplish the same goal with less
    “new” structured markup that may be obsolete or too custom as opposed to just shorthanding inside the existing img element.

    My hack is called Imajica and i’m still tinkering with it, clearly.

    • Hi Michael,

      Thanks for the reply. Enjoyed reading it.

      I do agree with the idea of simplifying the syntax. It should however stay clear and as semantic as possible.

      Let me know when your plugin reaches a further stage. I’d like to try it. :)

  19. Good work Koen!

    I think we came to the same conclusion that Responsive image loading is something that we desperately need nowadays with the variety of devices out there!

    I also tried to tackle this very problem and I created: BttrLazyLoading a Responsive Lazy Loading plugin for JQuery. http://bttrlazyloading.julienrenaux.fr

    Any feedback would be appreciated,
    Julien

    • Thanks Julien! Agreed, images are one of the biggest payloads on a website! I’ll have a look at your plugin and let ya know what I think.

      Let me know what your think of Responsive-img.js. :)

  20. This is pretty much exactly what I have been looking for, thanks.

  21. Angularjs recommends not to use the src attribute within the tag, but instead use the ng-src attribute which is the Angular way of binding a dynamic source to the src attribute. Since Angularjs is backed up by a huge community, I bet they have already thought about all the issues surrounding the absent scr attribute and they thought it is ok to do without.

  22. Great work here Koen!

    I am the founder of Pixtulate, a backend service to crop and format responsive images. We had to develop a similar script ourselves and it’s not easy. No one has really solved the browser pre-loading issue and we took the similar approach of using data-src as the image’s URL attribute.

    The only thing I’d like to add here maybe is that a service such as Pixtulate can also make your image formatting life a lot easier. You could use one and the same high resolution image and scale it on demand. No need to pre-scale anything.

    Consider the following code from Koen’s first sample:

    <img alt='kitten!' data-src-base='demo/images/' data-src='<480:kitten.jpg?w=480                                                                            
                                                              ,<768:small.jpg?w=768,
                                                              960:kitten.jpg?w=1200' />
    

    This code above, using Koen’s script, will actually work out of the box with Pixtulate.

  23. This approach is really good because it saves the size of the data transferred between the client and the server. Good for mobile devices where the connection speed matters the most. For the images where we don’t have control can be optimised by this tutorial
    http://www.techrecite.com/responsive-images-scaled-for-devices-how-to-do-it/

  24. Hi,
    I want to my website responsive design. But my four picture (Up to footer) is not reduce in mobile view. Because i called this picture in css. I want to reduce it Please tell me how it possible.
    Thanks
    Sohel Hossen

  25. Hi,
    I want to my website responsive design. But my four picture (Up to footer) is not reduce in mobile view. Because i called this picture in css. I want to reduce it Please tell me how it possible.
    Thanks
    Sohel Hossen

    url: https://dl.dropboxusercontent.com/u/225891136/Responsive%20Design%201/index.html

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!