July 28, 2014

BSprites: Combined web assets using Typed Arrays and Data URIs

Disclaimer: This was a weird idea I had one day and put this together the same evening. I've not tested it cross-browser or in a production environment. I've not benchmarked this either vs actually downloading all the images. It's kind of a 'what if' project at the moment. If you think it's really dumb or cool, I'd be really interested to hear your thoughts.

Generally, whenever a browser loads a new image, it will make a new HTTP request to the server. This doesn't always mean establishing a new TCP connection, because browsers will leave TCP connections open for reuse if they're to the same hostname. However, these connections are managed by a 'connection pool'. Most browsers do not do HTTP pipelining, which means the connection waits for a response before sending a new request. This means your requests are queued. If you need to load 300 images from the same hostname this could have serious implications for your user experience.

CSS sprites

A strategy that people frequently use to mitigate this is to visually combine multiple images into a single image, which can then be served with a single HTTP request. This requires someone to lay out the images onto a larger canvas, at known pixel coordinates. The image is then positioned using CSS in the browser to reveal the individual images.

CSS Sprite

The drawbacks of this method are fairly obvious:

Using data URIs

Browsers have moved on quite a lot in the time the CSS sprite hack was established. There are 2 new features that open up other possibilities.

Data URIs

Data URIs are a way to embed inline a resource as a base64 encoded string of the actual bytes that make up that resource. A data URI has the following form:


Data URIs can take the place of a URL in the src attribute of an image. Here's an example which is a small red dot:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />

Data URIs can also be used in CSS:

ul.checklist li.complete {
    padding-left: 20px;
    background: white url('data:image/png;base64,iVBORw0KGgoAA
P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC') no-repeat scroll left top;

This means if performance is critical to you, you can transfer a web page along with all images in a single HTTP request by including them inline in the HTML. Or with an extra request you can include them in the CSS.

Examples from Wikipedia

Disadvantages to using data URIs

There are however some disadvantages to the above:


Javascript is traditionally unsuited to dealing with binary data, but modern browsers have ArrayBuffer which is a data type that points to a fixed size raw allocation of bytes in memory. The ArrayBuffer type can't be manipulated directly in Javascript, but you can create a 'view' on a ArrayBuffer with Typed Arrays which can then be used to read/write data to the arraybuffer.

Typed arrays are like normal javascript arrays, except every element in the array is a fixed capacity number and they also have a buffer property that points to the underlying arraybuffer. The array elements will overflow, so if you have a Uint8Array with an element set to 255 and you add 1 to that element, it (and the underlying buffer) will overflow to 0.

In the example below, an ArrayBuffer is instantiated with 2 bytes of memory. All the bits are set to 0 by default.

When creating a Uint8Array from this buffer, we get an array, whose buffer property points at our original buffer and contains the elements [0,0].

Typed arrays

The XMLHttpRequest object in Javascript has a responseType property. This can be set to arraybuffer and will bypass any interpretation of the received data, instead just passing an arraybuffer object directly to the onload callback.

A new approach to sprites

The disadvantages of CSS Sprites and embedding images with data URIs inside HTML and CSS can be overcome by:

  1. Serving the images separately to any HTML/CSS
  2. Serving binary data rather than base64 encoded data
  3. Serving the combined resource dynamically based on input parameters

Server side

Server side

Client side

Example Implementations

Client side

github.com/mtharrison/bsprite-client - A simple browser module for requesting bsprites from a compliant server.

Server side

github.com/mtharrison/bsprite-go - A go package for creating and serving bsprites using net/http.

Node module coming soon too!

Comments powered by Disqus