« Back to Index

This is how BBC News currently implements it’s Image Enhancer for responsive images. Note: this is a completely rebuilt version of the code so the BBC’s original source code doesn’t actually look anything like the below example.

View original Gist on GitHub

Description.md

The BBC has a server-side image service which provides developers with multiple sized versions of any image they request. It works in a similar fashion to http://placehold.it/ but it also handles the image ratios returned (where as placehold.it doesn’t).

The original BBC News process (and my re-working of the script) follows roughly these steps…

The example code that follows is not production ready, you can’t just drop it into your code base and expect it to work. It is a rough prototype I built in about 30 minutes to help demonstrate in a roundabout way how the BBC News responsive team were handling the enhancement of images. The code uses features only available to more modern browsers (e.g. I’ve tested it in Chrome 28 and nothing else so your mile may vary if you’re using a different browser to look at the example).

I’ve also used the http://placehold.it/ service in place of the actual BBC News image provider. They don’t work in quite the same way, but if you open the following HTML code in a modern browser (e.g. Chrome) and resize the browser window downwards you’ll see the images change based on the current window dimensions. So rather than load a large image for smaller screens we swap out our images with images that are optimised for that screen size.

Notes about the rewritten example:

ImageEnhancer-Example.html

<!doctype html>
<html dir="ltr" lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>ImageEnhancer</title>
        <style>
            img {
                max-width: 100%;
            }

            a {
                display: block;
                margin-top: 1em;
            }
        </style>
    </head>
    <body>
        <p>Here is our main image, we load this regardless of JavaScript support.</p>
        <img src="http://placehold.it/340">

        <p>Below are three <code>div</code> elements that will lazy-load more images (as long as JavaScript is enabled).</p>
        <div class="delayed-image-load" data-src="http://placehold.it/340" data-width="340"></div>
        <div class="delayed-image-load" data-src="http://placehold.it/340" data-width="340"></div>
        <div class="delayed-image-load" data-src="http://placehold.it/340" data-width="340"></div>

        <script>
            var pubsub = (function(){
                var doc = document;
                var topics = {};
                var id = -1;
                var pubsub = {};

                pubsub.subscribe =  function (topic, fn) {
                    if (!topics[topic]) {
                        topics[topic] = [];
                    }

                    var token = (++id).toString();

                    topics[topic].push({
                        token: token,
                        fn: fn
                    });

                    return token;
                };

                pubsub.unsubscribe =  function (token) {
                    for (var m in topics) {
                        if (topics[m]) {
                            for (var i = 0, j = topics[m].length; i < j; i++) {
                                if (topics[m][i].token === token) {
                                    topics[m].splice(i, 1);
                                    return token;
                                }
                            }
                        }
                    }

                    return false;
                };

                pubsub.publish =  function (topic, data) {
                    if (!topics[topic]) {
                        return false;
                    }

                    setTimeout(function(){
                        var subscribers = topics[topic],

                        len = topics[topic].length;

                        while (len--) {
                            subscribers[len].fn(topic, data);
                        }
                    }, 0);

                    return true;
                };

                return pubsub;
            }());

            function ImageEnhancer(){
                pubsub.subscribe('imageEnhancer:resize', this.resizeImages.bind(this));
                pubsub.subscribe('div:added', this.changeDivsToEmptyImages.bind(this));
                pubsub.subscribe('div:changed', this.resizeImages.bind(this));
                this.availableWidthsFromOurImageProviderService = [96, 130, 165, 200, 235, 270, 304, 340, 375, 410, 445, 485, 520, 555, 590, 625, 660, 695, 736];
                this.divs = document.getElementsByClassName('delayed-image-load'); // we use `getElementsByClassName` so we get a live NodeList
                this.changeDivsToEmptyImages();
                window.requestAnimationFrame(this.init.bind(this));
            }

            ImageEnhancer.prototype = {
                changeDivsToEmptyImages: function(){
                    var i = this.divs.length;

                    while (i--) {
                        var div           = this.divs[i],
                            img           = document.createElement('img');
                            img.src       = 'data:image/gif;base64,R0lGODlhEAAJAIAAAP///wAAACH5BAEAAAAALAAAAAAQAAkAAAIKhI+py+0Po5yUFQA7';
                            img.className = 'image-replace';
                            img.width     = div.getAttribute('data-width');
                            img.setAttribute('data-src', div.getAttribute('data-src'));
                        
                        div.parentNode.replaceChild(img, div);
                    }

                    if (this.initialised) {
                        pubsub.publish('div:changed');
                    }
                },

                init: function(){
                    this.initialised = true;
                    this.resizeImages();
                    
                    window.addEventListener('resize', function() {
                        pubsub.publish('imageEnhancer:resize');
                    }, false);
                },

                resizeImages: function(){
                    var imageList = Array.prototype.slice.call(document.querySelectorAll('.image-replace'));

                    if (!this.isResizing) {
                        this.isResizing = true;

                        imageList.forEach(function (img) {
                            img.src = this.calculateNewImageSrc(img);
                        }.bind(this));

                        this.isResizing = false;
                    }
                },

                calculateNewImageSrc: function (img) {
                    var imageSrc      = img.getAttribute('data-src'),
                        imageWidth    = img.clientWidth,
                        selectedWidth = this.availableWidthsFromOurImageProviderService[0];

                    this.availableWidthsFromOurImageProviderService.forEach(function (currentlyAvailableWidth, index) {
                        if (imageWidth > currentlyAvailableWidth) {
                            selectedWidth = this.availableWidthsFromOurImageProviderService[index + 1];
                        }
                    }.bind(this));

                    return imageSrc.replace(/^(.+\/)\d+$/i, function (match, captured) {
                        return captured + selectedWidth;
                    });
                }
            };

            function createAnchor(){
                var anchor = document.createElement('a');
                    anchor.href = '#my_new_element';
                    anchor.innerHTML = 'Click me to add a new image to the DOM after ImageEnhancer has already been instantiated'

                document.body.appendChild(anchor);

                anchor.onclick = createNewImage;
            }

            function createNewImage(){
                var div = document.createElement('div');
                    div.className = 'delayed-image-load';
                    div.setAttribute('name', 'my_new_element');
                    div.setAttribute('data-src', 'http://placehold.it/340');
                    div.setAttribute('data-width', '340');

                document.body.appendChild(div);

                pubsub.publish('div:added');
            }

            createAnchor();

            new ImageEnhancer();

        </script>
    </body>
</html>