Handling Retina Images in a CMS

There are a few ways to handle the displaying of retina images such as sending a more compressed double resolution to all clients or a more webserver-based solution.

Both are great, but in a recent project for womenshealth.co.uk, I was using a CMS – Expression Engine and needed a different approach. .

Like most CMSs, Expression Engine generates a series of crops from one large ‘master’ file. So rather than one image and it’s double size retina counterpart, we have to think in terms of many file crops and the context of the image.

To do this, I wrote a JavaScript that, asks each image what size it wants to be and what pixel ratio the client is. Using this information, and having knowledge of all the different crops available on the server, we should be able to identify the perfect crop to fetch.

The script became even more useful because womanshealth.co.uk is a responsive site; so images need to be of varying size depending on the user’s browser window. I eventually added some re-size detection, so even if a browser window started tiny, forcing a thumbnail image to download, you wouldn’t be stuck with a nasty pixelated stretched thumbnail when the window is maximised.

A typical URL from the Expression Engine image plugin we used would look like this…

/images/423/buccament_bay_resort_st_vincent_caribbean__small_4x3.jpg

There is some specific code in my script to parse this pattern; you’ll have to amend this to match your CMS. Also, a crop object is defined (crops) – make sure you change that to match the crops you’re using in your CMS.

To work the script runs at the bottom of the page just before . There’s also an inline style in the head to temporarily prevent the natural image sizes loading, this will only kick in with js (using a modernizr selector), it is then removed when the script is done doing it’s stuff. Note I’ve given all images I want to process a class of “c” for “content”. UI images won’t be touched by this.

This is the inline style…

    <style type="text/css" id="retina-hider">
        .js img.c {display:none}
    </style>

Here’s the script with plenty of inline commentary…

(function(){
    //this little generic function enables us to have a sort of 'after resize' event ie we don't want to fire
    //loads of ret checks as we're resizing
    var delay = (function(){
        var timer = 0;
        return function(callback, ms){
            clearTimeout (timer);
            timer = setTimeout(callback, ms);
        };
    })();


    function ret(firstRun){
        //process retina images
        var dpr = 1, crop,currentCrop,bestCrop,aSplits;
        if(window.devicePixelRatio !== undefined) dpr = window.devicePixelRatio;
        //define crop object
        var crops = {thumb:152,small:320,medium:640,big:1112};

        $("img.c").each(function(i){
            var $img = $(this);
            //calculate image crop from filename
            //We're gonna be counting 2 _'s from the end of the filename to get the crop name, this bit is specific to our CMS
            aSplits = $img.attr("src").split("_");
            currentCrop =  aSplits[aSplits.length-2];
            for(crop in crops){
                if(  (crops[crop]>=$img.width()*dpr ||
                    crop=="big")
                    ){
                    if(currentCrop==crop||(crops[currentCrop]>crops[crop] && !firstRun ) ){
                        //we already have the optimal crop or the crop we have is bigger anyhow, (no need to download a smaller version) so exit this images each loop
                        //if first run it's ok to get a smaller crop because we have nothing anyhoo.
                        return true;
                    }
                    //we've found a crop that's bigger or equal, (always scale down)   or if we've reached the end (largest) and it's different crop from current one.
                    aSplits[aSplits.length-2]=crop;
                    $img.attr("src",aSplits.join("_"));
                    break;
                }
            }
        }) /**/
    }
    ret(true);
    $("#retina-hider").remove();
    //bind resize
    $(window).resize(function(){
        delay(function(){
           ret(false);
        }, 500);
    });
})()

This entry was posted in Uncategorized and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


six − = 1

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>