Technology Insights

Make Beautiful Icons With Image Sprites, CSS3 @font-face, or SVG

By Mary Becica / September 11, 2011

So you want to minimize page load time, yet still want a beautiful, responsive product? Getting both at once is a front-end engineer's never-ending quest. It begins with the decision for how to render graphics: will you use browser-based rastors (CSS), browser-based vectors (SVG), or rastorized images? A goal at AppDirect is to make branding a white-labeled marketplace smooth and painless while still giving end users a kick-ass experience. With this in mind, we looked into all three ways to build our front-end. I'm going to skip the graphics strategy as a whole and just focus on icons. Icons are crucial to making a polished app interface, and can be implemented in a variety of ways.

Images Sprites

A popular method for optimizing image-based icons is to use image sprites:

  • Sprites combine many icons into one image file to reduce HTTP requests.
  • Icons can be made with custom photoshop-made graphics.
  • Animation, effects, or style changes have to be done with additional images.
  • Short and concise HTML and CSS.
  • Redesigns, front-end changes and design improvements make a sprite difficult to maintain.

Typical sprite HTML:

<div class="icon add"></div>
<div class="icon subtract"></div>
And CSS:
.icon {
 background:url(icons.png) no-repeat 0 0;
 width:16px;
 height:16px;
}
.icon.add { background-position:-20px 0px; }
.icon.subtract { background-position:-20px -20px; }

Overall, a great solution. Sprites are awesome. But what if you demand more maintainability, or you have a lot of sprites with repeated icons in different colors?

CSS3 and @Font-Face

CSS3-and-font-face.png

CSS3 is more than just dropshadows and rounded corners (check out these great tools in case you want those). There is also the @font-face, which you can use to make icons by choosing a glyph font family. @font-face HTML:

<div class="icon add"></div>
<div class="icon subtract"></div>

And CSS:

.icon {
 background:url(icons.png) no-repeat 0 0;
 width:16px;
 height:16px;
}
.icon.add { background-position:-20px 0px; }
.icon.subtract { background-position:-20px -20px; }

The text in the HTML is literally being replaced by a vector character. Essentially:

  • No images to load, but a smattering of font files will have to be available for cross-browser support.
  • You can completely customize what glyphs go into your font.
  • Animation and effects are limited to CSS (shadows, font-size, color).
  • No gradients (this is a big downer).
  • The HTML and CSS becomes more complex for browser compatibility.

Overall, I like the @font-face solution, but I'm not satisfied with its limitation on gradients and its esoteric browser support. Plus, if you're going for an out-of-the-box icon set, the font files get pretty large.

SVG

For the purposes of brevety, I'm not going to cover all of SVG but stick to the RaphaelJS library by Dmitry Baranovski, which has a great API and the added bonus of full IE-compatibility. I'm also going to be relying a bit on jquery to keep things short.

SVG-icons.png

A custom SVG implementation requires some legwork in the beginning, but it might be worth your while:

  • Similar to the font-face solution in that the icons are vector based.
  • Full SVG animation possibilities and effects, as well as CSS styling (although glow does not play very nice right now).
  • Smal code footprint, no images.
  • Full browser compatibility with RaphaelJS without any hacks necessary.
  • Requires custom javascript and javascript maintanence.
  • Easy to add, remove and edit icons from the set.
  • Easy to convert an existing sprite-based icon project to SVG-based.

Here's a breakdown of how it can be done:

  • 1: Get a vector-based icon set and save the path attributes. We use IconSweets, which is a fantastic free set, but you can also check out Dmitry's icon set for Raphael.Save your icon set path information (this is the d="" variable in SVG). For example:
iconPaths = {
 heart: "M15.999,22.77l-8.884,6.454l3.396",
 plus: "M25.979,12.896 19.312,12.896 19.312,6.229 12.647,6.229",
 minus: "M25.979,12.896,19.312,12.896,5.979,19.562z"
}
  • 2: Prepare your HTML and have your icon classes readily accessible. If you did previously use a sprite, your HTML is probably ready. With jquery or other dom-manipulation library of choice, if you run $('.icon').each() you'll get a list of all of the icons on the page, and from there a list of all of the icon class names (you could also just nab this from your CSS, if you don't want to do it dynamically). For
<div class="icon add"></div>
<div class="icon subtract"></div>
<div class="icon heart"></div>

Your classes would be:

["add","subtract", "heart"]
Keep this in mind for step 3.
  • 3: Define custom styles and associate your HTML classes with your icon set. Another javascript object can be used to connect the icons from step 1 and 2:
iconMap = {
 "add": { // this is the class name
 iconName: "plus", // the icon name
 attrs: {
 translation: "5,5",
 scale:".8,.8",
 fill:"green"
 }
 },
 "subtract": {
 iconName: "minus"
 },
"heart": {
 iconName: "heart"
 }

This is nice for a few reasons, the first and foremost being that your iconPaths object can stay lean and the iconMap object can do a the nitty gritty styling. You can also add additional styles with CSS later. If you want your life (and code) to be awesome, you will name your icons and your HTML classes the same name, and eliminate the need for the iconName attribute.

  • 4: Implement! First, write the function that builds the actual SVG. Let's assume this function takes in the icon dom object from the HTML. Please note that this is is mostly sudo code and does not work if you copy and paste it.
convertSVG = function(icon) {
 var name = getClassName(icon), // Get the name of the icon.
 // I'll let you write getClassName.
 r = Raphael(icon[0], icon.width, icon.height); // Draw a raphael canvas
 r.path(iconPath[iconMap[name].iconName]).attrs(iconMap[name].attrs);
}

The last line there is drawing the actual SVG using Raphael's path() function, which needs the path string you defined in step 1, then attrs() applies your custom styles. Time to load it up on your page! Without optimization for speed, you could literally just run that each statement on page load:

$(".icon").each($(this).convertSVG());

I suggest adding some checks to only run the function if the icon hasn't been drawn on the page already - or run convertSVG() for more specific icons.

Conclusion

For AppDirect, it was imperitive that we move away from image-based CSS to build our private-label marketplaces. Generating large sprites of icons is a risky time-sink and not maintainable across many skins - our most cherished skill is being able to improve our product with speed and agility, and our customers deserve those improvements no matter what marketplace they are in. We needed something better than static images, and it had to be able to load fast, too. The load-time differences between images, font files, and javascript files are going to depend on your site - a highly customized solution is certainly not recommended for all situations, and should be optimized within your product's environment. For AppDirect, custom SVGs fit the private-label bill perfectly. Since we've implemented it, adding and editing icons has been quick and painless - usually a few added javascript attributes produces a new icon with custom styles across multiple sites. Good luck making your front-end lighter and faster! The more we demand our browsers generate beautiful graphics, the better our browsers will be!

Resources