Truly Responsive Images with responsive-images.js
Responsive Images
Truly Responsive Images with responsive-images.jsResponsive 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.



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. :)






    
    
    
        
    
    
    
        
    
    






About Koen VendrikKoen 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.blog.kvendrik.comkvendrikPosts