Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with:

Embedding Images As Base64-Encoded Data URIs Using Less CSS

By Ben Nadel on
Tags: HTML / CSS

One of the things that I really like about Less CSS is the ability to embed images directly in the generated CSS files using data URIs. A data URI allows one type of content (a CSS file in our case) to embed another type of content (images in our case). Since images are binary, the image content has to be be Base64-encoded so that it can be embedded as text. The syntax is a bit funky; but, it doesn't matter because Less CSS handles it for you. And, the good news is, data URIs are supported back to IE8 (with size-limit caveats).

To demonstrate this feature, I'm going to embed some pictures of monkeys. But, simply using the data-uri() method wouldn't be fun; so, instead, I'm going to loop over a list of image URLs and then dynamically generate a CSS class for each image.

  • // I loop over the given list and execute the given ruleset for each item.
  • .list-loop( @list, @ruleset ) {
  •  
  • // When we define variables inside of a mixin, then get automatially injected into
  • // the calling context. This is problematic if we ever want to call this mixin more
  • // than once since each subsequent invocation will fail to inject variables (since
  • // they cannot overwrite each other). By wrapping the body of this mixin in a block
  • // scope, we prevent the automatic variable injection from bubbling up to the calling
  • // context. As such, they will be scoped locally to this mixin and to any nested
  • // block-scope which can inherit them - IE, our @ruleset.
  • & when ( true ) {
  •  
  • @length: length( @list ) ;
  •  
  • .loop( 1 ) ;
  •  
  • .loop( @index ) when ( @index <= @length ) {
  •  
  • // Extract the item from the list at the given position.
  • @it: extract( @list, @index ) ;
  •  
  • // Invoke the given ruleset - this will inherit the "@it" variable.
  • @ruleset() ;
  •  
  • .loop( @index + 1 ) ;
  •  
  • }
  •  
  • }
  •  
  • } ;
  •  
  • // Define the list of images that we want to embed.
  • @images:
  • "../images/monkey-1.jpg"
  • "../images/monkey-2.jpg"
  • ;
  •  
  • // For each image in the list, create a derived class with an EMBEDDED image. The
  • // data-uri() method embeds images as Base64-encoded data URIs.
  • .list-loop(
  • @images,
  • {
  • .monkey-@{index} {
  • background-image: data-uri( @it ) ;
  • debug-url: @it ;
  • height: 300px ;
  • width: 300px
  • }
  • }
  • ) ;
  •  
  • // Loop AGAIN to demontate that the variables in the list-loop didnt escape scope.
  • .list-loop(
  • @images,
  • {
  • .monkey-@{index} {
  • background-image: data-uri( @it ) ;
  • debug-url: @it ;
  • height: 300px ;
  • width: 300px
  • }
  • }
  • ) ;

Here, we're leveraging the ability to pass a Less CSS ruleset to a mixin for "execution". You'll notice that I am calling the list-loop() mixin twice; this is to demonstrate the fact that the mixin-based variables aren't leaking up into the calling context, only down into the ruleset invocation. Within each ruleset, the list-loop() mixin is exposing the "@it" variable which contains the image URL. This URL is then being embedded using the data-uri() method.

When we compile the given Less CSS, we get the following CSS output (Note: I have truncated the data URIs for display):

  • .monkey-1 {
  • background-image: url("data:image/jpeg;base64,/9j/....E5oSy6gpzQEGxaZv//Z");
  • debug-url: "../images/monkey-1.jpg";
  • height: 300px ;
  • width: 300px;
  • }
  • .monkey-2 {
  • background-image: url("data:image/jpeg;base64,/9j/....t2DAHg1NQLkHrNMD//Z");
  • debug-url: "../images/monkey-2.jpg";
  • height: 300px ;
  • width: 300px;
  • }
  • .monkey-1 {
  • background-image: url("data:image/jpeg;base64,/9j/....E5oSy6gpzQEGxaZv//Z");
  • debug-url: "../images/monkey-1.jpg";
  • height: 300px ;
  • width: 300px;
  • }
  • .monkey-2 {
  • background-image: url("data:image/jpeg;base64,/9j/....t2DAHg1NQLkHrNMD//Z");
  • debug-url: "../images/monkey-2.jpg";
  • height: 300px ;
  • width: 300px;
  • }

Pretty cool stuff!

The nice thing about this approach is that it reduces the number of HTTP requests that need to be made to gather the content (since the content is partially embedded in the CSS). Of course, there are trade-offs to be considered around things like upfront load time, deferred loading, and retina images. But, that's beyond the scope of this post.




Reader Comments

Thanks for the post =), using this for a long time now (without less, just php minifing/concatinating/datauriing etc. script)...just wanted to note, that a drawback is also the inability of the browser to cache the image.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.