CSS Sprites Revisited

About The Author

Niels Matthijs spends his spare time combining his luxury life with the agonizing pressure of blogging under his Onderhond moniker. As a front-end developer he … More about Niels ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

I’m pretty confident that I won’t surprise anyone here by saying that CSS sprites have been around for quite a while now, rearing their somewhat controversial heads in the Web development sphere as early as 2003.

I’m pretty confident that I won’t surprise anyone here by saying that CSS sprites have been around for quite a while now, rearing their somewhat controversial heads in the Web development sphere as early as 2003.

Still, the CSS sprite hasn’t truly found its way into the everyday toolkit of the common Web developer. While the theory behind CSS sprites is easy enough and its advantages are clear, they still prove to be too bothersome to implement, especially when time is short and deadlines are looming. Barriers exist to be breached, though, and we’re not going to let a couple of tiny bumps in the road spoil the greater perks of the CSS sprite.

Further Reading on SmashingMag:

If you want more background information on best practices and practical use cases, definitely read “The Mystery of CSS Sprites: Techniques, Tools and Resources.” If you’re the defensive type, I would recommend “CSS Sprites: Useful Technique, or Potential Nuisance?,” which discusses possible caveats.

I won’t take a stance on the validity of CSS sprites. The aim of this article is to find out why people still find it difficult to use CSS sprites. Also, we’ll come up with a couple of substantial improvements to current techniques. So, start up Photoshop (or your CSS sprite tool of choice), put on your LESS and Sass hats, and brush up your CSS pseudo-element skills, because we’ll be mixing and matching our way to easier CSS sprite implementation.

The Problem With CSS Sprites

While Photoshop is booting, take a moment to consider why CSS sprites have had such a hard time getting widespread acceptance and support. I’m always struggling to find the correct starting position for each image within a sprite. I’ll usually forget the exact coordinates by the time I need to write them down in the style sheet, especially when the image is located at x:259, y:182 and measures 17×13 pixels.

I’m always switching back and forth between Photoshop and my CSS editor to write down the correct values, getting more and more frustrated with every switch. I know software is out there specifically tailored to help us deal with this, but I’ve never found an application that lets me properly structure its CSS output so that I can just copy and paste it into my CSS file.

Another important thing to realize is that CSS sprites are meant to be one part of our website optimization toolkit. Many of the people who write and blog about CSS sprites are comfortably wearing their optimization hats while laying out best practices, often going a little overboard and losing track of how much effort it requires to implement their methods of choice.

This is fine if you’re working on an optimization project, but it becomes counterproductive when you need to build a website from scratch while fighting tight deadlines. If you have time to truly focus on implementing a CSS sprite, it’s really not all that hard; but if you have to juggle 50 other priorities at the same time, it does turn into a nuisance for many developers. With these two factors in mind, we’ll try to find a way to make image targeting easier, while coming to terms with the fact that sometimes we have to sacrifice full optimization in the name of ease of development.

CSS sprites in the wild: Amazon, Google and Facebook.
CSS sprites in the wild: Amazon, Google and Facebook.

Preparing The Sprite

If you look online for examples of CSS sprites, you’ll see that most are optimized for an ideal use of real estate—gaps between images are kept to a minimum in order to keep the load of the entire sprite as low as possible. This reduces loading time considerably when the image is first downloaded, but it also introduces those horrible image coordinates that we mentioned earlier. So, why not take a slightly different approach? Let’s look for an easier way to target images while making sure the resulting sprite is not substantially bigger than the sum of its individual images.

Rather than try to stack the images in such a way that makes the resulting sprite as small as possible, let’s try a different layout. Instead of a random grid, we’re going to build a nice square grid, reserving the space of each square for one image (although larger images might cover multiple squares). The goal is to be able to target the starting point of every image (top-left corner) with a very simple mathematical function.

The size of the grid squares will depend on the average dimension of the images that you’ll be spriting. For my website, I used a 32×32 pixel grid (because only one image exceeds this dimension), but grid sizes will vary according to the situation. Another thing to take into account is the image bleeding that can occur when zooming a page; if you want to play it safe, add a little padding to each image within the sprite. For extra optimization, you could also use a rectangular grid instead of a square grid, further reducing any wasted space; while a tad more complex, this isn’t much different from what we’ll be doing. For this article, though, we’ll stick with the square grid.

Preparing The Sprite: A Photoshop Bonus

I’m no designer, so I’m not sure how applicable this section is beyond the realm of Photoshop, but there are still some noteworthy improvements that can be made before we get to the actual code. First of all, visualizing the grid can be of great help. Photoshop has guides to do this (so you don’t actually have to include the markers in your sprite), with the added bonus that layers will snap to these guides (making pixel-perfect positioning of each individual image much easier).

Manually adding these guides can be somewhat of a drag, though. Luckily, a colleague of mine (hats off to Filip Van Tendeloo) was kind enough to write and share a little Photoshop script that builds this grid of guides automatically based on the base size of your square. Pretty convenient, no? Just download the script, save it in the PresetsScripts directory, and choose File → Scripts → Sprite Grid from the Photoshop menu.

Finally, to really finish things off, you can add numbers to the x and y axes of your grid so that you can easily pinpoint every square in the grid. Don’t add these numbers to the actual sprite, though; just copy the sprite into a new image and add them there. These numbers are just for reference—but trust me, they will definitely come in handy in a minute.

Example of a reference image.
Example of a reference image.

If you can’t (or don’t want to) use preprocessing languages such as LESS and Sass, just add the coordinates of each square instead.

LESS And Sass To The Rescue

With all of the images on the same square grid, we can now easily compute the top-left coordinate of each image (this coordinate is the exact match needed for the background-position values to reposition the sprite). Just take the base size of the grid and multiply it by the numbers that you just added to your reference image. Say you need to target the image at a position of (5,3), and your grid size is 32 pixels; the top-left position of your image will be 32 × (5,3) = (160,96). To add these values to your CSS file, just use their negative equivalents, like so:

background-position: -160px -96px;

Leave your calculator where it is for the time being, because it would be too much of a hassle. If computers are good at anything, it’s doing calculations, so we’re going to make good use of that power. Plain CSS doesn’t allow us to do calculations (yet), but languages like LESS and Sass were made to do just that. If you need more background information, check out the article “An Introduction to LESS and Comparison to Sass.” While both languages offer similar functionality, their syntaxes are slightly different. I’m a LESS man myself, but if you’re used to Sass, converting the following examples to Sass code shouldn’t be too hard. Now for some advanced CSS magic:

@spriteGrid: 32px;
.sprite(@x, @y) {
   background: url(img/sprite.png) no-repeat;
   background-position: -(@x*@spriteGrid) -(@y*@spriteGrid);
}

Nothing too exciting so far. First, we’ve defined the grid size using a LESS variable (@spriteGrid). Then, we made a mixin that accepts the numbers we added to the reference image earlier. This mixin uses the grid variable and the image’s coordinates to calculate the base position of the image that we want to target. It might not look fancy, but it sure makes the basic use of sprites a lot easier already. From now on, you can just use .sprite(1,5), and that’s all there is to it. No more annoying calculations or finding random start coordinates.

Of course, the mixin above only works in our somewhat simplified example (using a square grid and just one sprite). Some websites out there are more complex and might require different sprites using rectangular grids for additional optimization. This isn’t exactly impossible to fix with LESS, though:

.spriteHelper(@image, @x, @y, @spriteX, @spriteY) {
   background: url("img/@{image}.png") no-repeat;
   background-position: -(@x*@spriteX) -(@y*@spriteY);
}
.sprite(@image, @x, @y) when (@image = sprite1), (@image = sprite3){
   @spriteX: 32px;
   @spriteY: 16px;
   .spriteHelper(@image, @x, @y, @spriteX, @spriteY);
}
.sprite(@image, @x, @y) when (@image = sprite2){
   @spriteX: 64px;
   @spriteY: 32px;
   .spriteHelper(@image, @x, @y, @spriteX, @spriteY);
}

Yes, this looks a bit more daunting, but it’s still pretty basic if you take a minute to understand what’s going on. Most importantly, the added complexity is safely hidden away inside the LESS mixins, and we’re still able to use the same sprite mixin as before, only now with three parameters in total. We’ve just added an image parameter so that we can easily determine which sprite to use each time we call our mixin.

Different sprites might have different grid dimensions, though; so, for each grid dimension, we need to write a separate LESS mixin. We match each sprite to its specific dimension (the example above handles three different sprites and matches it to two different grid dimensions), and we define the dimensions (@spriteX and @spriteY) locally in each mixin. Once that is done, we offload the rest of the work to a little .spriteHelper mixin that does all of the necessary calculations and gives us the same result as before.

The “guards” (as LESS calls them—characterized by the keyword “when”) are quite new to the LESS vocabulary, so do make sure you’re using the latest version of the LESS engine if you attempt to run this yourself. It does offer a great way to keep the sprite mixin as clear and concise as possible, though. Just pass the sprite you want to use to the sprite mixin, and LESS will figure out the rest for you.

Common CSS Sprite Use Cases

With these mixins at our disposal, let’s see what different CSS sprite use cases we can identify and find out whether capturing these use cases in additional LESS mixins is possible. Once again, in order to minimize complexity in the upcoming sections, we’ll assume you’re working with a square grid sprite. If you need a rectangular grid, just add the image’s parameter to the LESS mixins we’ll be working with, and everything should be fine.

For each use case, we’ll define two mixins: one with and one without height and width parameters. If you’re like me and you keep misspelling the height and width keywords, the second mixin might come in handy. It does leave you the responsibility of learning the correct order of the parameters, though. LESS allows you to define these mixins in the same document, however, so there’s really no harm in listing them both, picking whichever is easiest for you.

1. Replaced Text

This is probably the easiest use case, occurring when we have an html element at our disposal that can be given a fixed dimension (in order to ensure we can hide the unwanted parts of the sprite). We’ll also be hiding the text inside the html element, replacing it with an image from our sprite. Typical examples of this use case are action links (think delete icons, print links, RSS icons, etc.) and headings (although CSS3 and Web fonts are quickly making this technique obsolete).

.hideText{
   text-indent: -999em;
   letter-spacing: -999em;
   overflow: hidden;
}
.spriteReplace(@x, @y) {
   .sprite(@x, @y);
   .hideText;
}
.spriteReplace (@x, @y, @width, @height) {
   .sprite(@x, @y);
   .hideText;
   width: @width;
   height: @height;
}

The spriteReplace mixin simply wraps our former sprite mixin and adds a small helper mixin to hide the text from view. It’s pretty basic, but it does save us the trouble of adding the .hideText mixin manually for every instance of this use case.


Using sprites for replaced elements.

In the example above, we have a list of sharing options. For whatever reason (theming or just personal preference), let’s say we decide to use CSS backgrounds instead of HTML images. Nothing to it with the mixins we just created. Here’s the code (assuming we’re using the reference image shown earlier):

<ul class="sharing">
   <li class="twitter"><a href="#">Share [article’s title] on Twitter</a></li>
   …
</ul>
.sharing .twitter a {
   .spriteReplace(0, 0, 32px, 32px); display:block;
}

2. Inline Images

For the second use case, we’ll tackle inline images. The main problem we’re facing here is that we won’t be able to put fixed dimensions on the html element itself because we’re dealing with variable-sized content. Typical uses of inline images are for icons next to text links (for example, external links, download links, etc.), but this method can also be used for any other item that allows text to be wrapped around a sprite image.

.spriteInline(@x, @y) {
   .sprite(@x, @y);
   display: inline-block;
   content: "";
}

.spriteInline(@x, @y, @width, @height) {
   .sprite(@x, @y);
   display: inline-block;
   content: "";
   width: @width;
   height: @height;
}

We might be lacking a structural element to visualize our image, but 2011 taught us that pseudo-elements are the perfect hack to overcome this problem (Nicolas Gallagher’s eye-opening article on using pseudo-elements for background images explains everything in detail). This is why the spriteInline mixin was especially developed to be used within a :before or :after selector (depending on where the image needs to appear). We’ll add an inline-block declaration to the pseudo-element so that it behaves like an inline element while still allowing us to add fixed dimensions to it, and we’ll add the content property, which is needed just for visualizing the pseudo-element.


Using sprites for inline images.

The example above shows a list of affiliate links. The design demands that each link be limited to one line, so we can safely use the .spriteInline mixin to display the icons in front of each link:

<ul class="affiliates">
   <li class="amazon"><a href="#">Buy on Amazon.com</a></li>
   …
</ul>
.affiliates .amazon a:before {
   .spriteInline(4, 1, 22px, 16px);
}

3. Padded Images

The third and final use case comes up when text isn’t allowed to wrap around a sprite image. Typical examples are list items that span multiple lines, and all kinds of visual notifications that bare icons. Whenever you want to reserve space on a multi-line element to make sure the text lines up neatly next to the image, this is the method you’ll want to use.

.spritePadded(@x, @y) {
   .sprite(@x, @y);
   position: absolute;
   content: "";
}
.spritePadded(@x, @y, @width, @height) {
   .sprite(@x, @y);
   position: absolute;
   content: "";
   width: @width;
   height: @height;
}

Again we’ll try our luck with pseudo-elements; this time, though, we’ll be performing some positioning tricks. By applying a position: absolute to the pseudo-element, we can place it right inside the space reserved by its parent element (usually by applying padding to the parent—hence, the name of the mixin). The actual position of the image (the left, right, top and bottom properties) is not added to the spritePadded mixin and should be done in the selector itself to keep things as clean and simple as possible (in this case, by maintaining a low parameter count).

Because we’re using absolute positioning, either the :before or :after pseudo-element will do the job. Keep in mind, though, that the :after element is a likely candidate for CSS clearing methods, so if you want to avoid future conflicts (because the clearing fix won’t work if you add a position: absolute to the :after pseudo-element), you’d be safest applying the sprite style to the :before element.


Using sprites for padded elements.

Let’s assume we need to indicate that our article is available in other languages (probably on a different page or even website). We’ll add a little notification box, listing the different translations of the current article. If the text breaks onto a second line, though, we do not want it to crawl below our little icon, so we’ll use the spritePadded mixin:

<section class="notification translated">
   <p>Translation available…</p>
</section>
.translated p {
   padding-left: 22px;
   position: relative;
}
.translated p:before {
   .spritePadded(5, 5, 16px, 14px);
   left: 0;
   top: 0;
}

The Ideal Sprite

So, have we achieved CSS sprite wizardry here? Heck, no! If you’ve been paying close attention, you might have noticed that the use cases above offer no solution for adding repeating backgrounds to a sprite. While there are some ways around this problem, none of them are very efficient or even worthy of recommendation.

What CSS sprites need in order to truly flourish is a CSS solution for cropping an image. Such a property should define a crop operation on a given background image before the image is used for further CSS processing. This way, it would be easy to crop the desired image from the sprite and position it accordingly, repeating it as needed; we’d be done with these :before and :after hacks because there wouldn’t be anything left to hide. A solution has been proposed in the Editor’s Draft of the CSS Image Values and Replaced Content Module Level 3 (section 3.2.1), but it could take months, even years, for this to become readily available.

For now, the LESS mixins above should prove pretty helpful if you plan to use CSS sprites. Just remember to prepare your sprite well; if you do, things should move ahead pretty smoothly, even when deadlines are weighing on you.

Smashing Editorial (al)