By Oleksii Rudenko November 18, 2014 12:14 AM
Experiments: Using Image Sprites and IMG Tags to Display a Set of Images

Dear Readers,

as you may know, the css-sprite technique helps to reduce the number of requests to get images from the server. This is achieved by combining all images that are needed to style a web page (e.g. icons, backgrounds) into one big image. Then by specifying coordinates, the width and the height of a part of the image, browsers can render pieces of this big image in appropriate places of a web page. This is how it looks like in the code:

See the Pen jEXJQM by Oleksii Rudenko (@OrKoN) on CodePen.

In this blog post, I will not use the CSS background to display sprites as it is usually done to style Web pages. Instead, I will try to use regular IMG tags and sprites to render images which are part of the content. For example, given a list of items (e.g. products) - does it make sense to combine all product images into a single sprite image and load all images at once? We will see in the end.

The solution is not purely a client side one. It also requires some server side coding. The client side will be implemented using Ember.js and the server will be coded in node.js using koa.js framework.

Implementing The Server

The server is very simple. It uses koa.js framework. First, let’s set up an example without using any sprites. Like one would normally do when displaying a collection of items:

var koa = require('koa');
var app = koa();
var items = [
    {
  src : '../public_html/imgs/1.jpg'
    },
    {
  src : '../public_html/imgs/2.jpg'
    },
    {
  src : '../public_html/imgs/3.jpg'
    },
    {
  src : '../public_html/imgs/4.jpg'
    },
    {
  src : '../public_html/imgs/5.jpg'
    },
    {
  src : '../public_html/imgs/6.jpg'
    },
    {
  src : '../public_html/imgs/7.jpg'
    },
    {
  src : '../public_html/imgs/8.jpg'
    },
    {
  src : '../public_html/imgs/9.jpg'
    },
];
function getImgs() {
    items.forEach(function (item) {
        item.src = item.src.replace('../public_html/', ''); // some processing to provide meaningful URLs to the client
    });
    return result;
}
app.use(function * () {
    this.set('Access-Control-Allow-Origin', '*'); // to allow CORs requests
    this.body = getImgs();
});
console.log("Listening");
app.listen(3000);

Implementing The Client

I will use the Ember’s Starter Kit package and put some basic templates in it:

See the Pen VYqRVq by Oleksii Rudenko (@OrKoN) on CodePen.

Adding sprites

Adding sprites requires several steps:

  • on the server: generate sprites for images in the items array
  • on the client: render the img tag using the sprite image

First, I update the package.json file and add the sprititize module as a dependency. sprititize is a simple module I’ve written, that takes a collection of items, finds their images and builds one big sprite image using the.

Server:

{
  "name": "sprites",
  "description": "sprites",
  "version": "0.0.1",
  "dependencies": {
      "koa" : "*",
      "sprititize": "OrKoN/sprititize"
  }
}

server.js

var koa = require('koa');
var app = koa();
var makeSprites = require('sprititize');
var items = [
    {
  src : '../public_html/imgs/1.jpg'
    },
    {
  src : '../public_html/imgs/2.jpg'
    },
    {
  src : '../public_html/imgs/3.jpg'
    },
    {
  src : '../public_html/imgs/4.jpg'
    },
    {
  src : '../public_html/imgs/5.jpg'
    },
    {
  src : '../public_html/imgs/6.jpg'
    },
    {
  src : '../public_html/imgs/7.jpg'
    },
    {
  src : '../public_html/imgs/8.jpg'
    },
    {
  src : '../public_html/imgs/9.jpg'
    },
];
function *getSpriteImgs() {
    yield makeSprites(items, { // making use of the sprititize module
        output: '../public_html/imgs/sprite.jpg'
    });
    var result = [];
    for (var i = 1; i <= 9; i++) {
        result.push({
            "src": "imgs/sprite.jpg",
             "id" : "sprite" + i
        });
    }
    return result;
}
app.use(function * () {
    this.set('Access-Control-Allow-Origin', '*');
    this.body = yield getSpriteImgs();
});
console.log("Listening");
app.listen(3000);

Client

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Ember Starter Kit</title>
        <link rel="stylesheet" href="css/normalize.css">
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <script type="text/x-handlebars">
            <h2>Welcome to Sprites Demo</h2>
            {{outlet}}
        </script>
        <script type="text/x-handlebars" id="components/sprite-img">
            <div {{bind-attr id=item.id}}>
                <img {{bind-attr src=item.src}}>
            </div>
        </script>
        <script type="text/x-handlebars" id="index">
            <ul>
            {{#each item in model}}
            <li>
                {{sprite-img item=item}}
            </li>
            {{/each}}
            </ul>
        </script>
        <script src="js/libs/jquery-1.10.2.js"></script>
        <script src="js/libs/handlebars-1.1.2.js"></script>
        <script src="js/libs/ember-1.7.0.js"></script>
        <script src="js/app.js"></script>
    </body>
</html>

And some yet important styles:

div[id^='sprite'] {
    display: block;
    height: 256px;
    width: 256px;
    overflow: hidden;
    position: relative;
}
div[id^='sprite'] img {
    position: absolute;
}
#sprite1 img {
    top: 0px;
}
#sprite2 img {
    top: -256px;
}
#sprite3 img {
    top: -512px;
}
#sprite4 img {
    top: -768px;
}
#sprite5 img {
    top: -1024px;
}
#sprite6 img {
    top: -1280px;
}
#sprite7 img {
    top: -1536px;
}
#sprite8 img {
    top: -1792px;
}
#sprite9 img {
    top: -2048px;
}

See the Pen embXbE by Oleksii Rudenko (@OrKoN) on CodePen.

Performance

According to my simple and not reproducible benchmarks :-) , the code using sprites was 1.5-1.8 times faster when tested locally. So performance-wise it may be beneficial especially if there are lots of images to be shown on a page and they are part of the content. Nevertheless, this approach has several drawbacks.

Drawbacks

  • the resulting HTML is not semantically correct - i.e., when some one would try to parse your website, it will get the same image for all items and the information about which part of the image should be shown for a particular item most likely will be lost
  • the approach may not work well with responsive images supported by HTML 5
  • the approach may be slower on slow network connections [never tested]
  • I would not recommend to use this approach in production unless you’re sure it really helps

Further Improvments

Caching may help to reduce the amount of cpu resources required to generate a sprite. For example, if images shown on a page are always the same (e.g. 10 the most popular products) it makes sense to cache the sprite image so there is no overhead except for the first request. If each item associated with an image or the image itself has an unique ID, this ID may be used to define the spire ID. For example, products #3, #4, #5, #6 would result in a sprite 3_4_5_6.jpg. Thus, one can easily check whether there is a sprite image for a given set of items already.

The CSS is awful and it’s used only to demonstrate the approach. A more compact and general CSS may be defined. Also the coordinates of the images with the sprite may be provided by the server as it knows them. Thus, there will be no need to define them in the CSS beforehand.

Have fun!