If you’re a front end developer you probably encountered both approaches at least a couple of times. There are a lot of great articles about that. You can read all about the problems with correct sizing and positioning, the need to take care of many small details when creating character sets, and many more.
If you want to skip the introduction on how the process was defined and what were the main issues then just go ahead and click here.
The problem
In BT we use a common set of icons in nearly all of our projects. Usually when new designs for a new undertaking emerge we receive a bunch of new symbols using those we need to extend the preexisting bunch. Up until recently, that task was handled in few steps:
- uploading old & new icons to icomoon
- generating & downloading new icon font
- updating our CDN with new font files
- going to each and every project & checking if everything looks fine, usually with the added bonus of fixing character positions to match the new order
Sure, this workflow could be improved – no doubt about that. We probably could figure out a better way, that e.g. includes an updated stylesheet on the CDN which we then link in our projects. However, as is the custom in our line of work, we decided to do it next time.
Idea and prerequisites
Next time finally arrived but with a complete overhaul of the existing system. There were a couple of constraints that were needed to be taken into consideration.
First of all, no HTML modifications. We have a lot of places where we use externally generated DOM structure and either have very little control over the output, or we just don’t want to override every single block of code to change one icon. This applies for both DOM elements and their classes.
Being able to cover the cases of icons as separate DOM elements (`<i class=”icon-something”>`) and those added before existing content (`<span class=”caption”>Caption</span>`).
Just looking at those two it was obvious we couldn’t use the most ideal solution – inline images (`<svg>`, `<use>`, etc). We needed something that will behave very similar to the existing solution. Natural course of action was going for background images.
The idea of a JavaScript replacement library was briefly entertained, but there was too much inconsistency in the way icons were used across the projects (e.g. classes without any `icon-` or `i-` prefix) to implement it in a proper way.
Quirks to solve
Two main issues were encountered: using different colors, and scaling in relation to the context icons are used in.
The former was quickly solved with some help from the great Ian Feather (http://ianfeather.co.uk/). A spec of all the colors we use across BT was prepared. Sadly there is no general style guide that limits the amount but the designers are doing a really good job in terms of consistency, so we ended up with 10 different colors (mostly shades of grey…). Then ‘mini-sprites’ of each icon in all of those variations were generated to be used in the end transformations.
The latter problem took some trials and errors. I ended up needing to tweak the source icons to be in more or less standard proportions (1:1, 1.5:1, 2:1). Then I included these proportions in the stylesheet that would be later included in the bundle. The code was quite simple:
.icon-square { width: 1em; height: 1em; } .icon-rectangle { width: 2em; height: 1em; }
The important thing was always keeping the height set to `1em`, so the icon wouldn’t be larger than the surrounding content. It also allows us to control it using `font-size`, so all the CSS rules previously used for icon fonts could still apply! That was a really big win and it removed the necessity to set the dimensions for each non-standard icon separately.
What I ended up doing is creating a workflow consisting of a couple of steps that allowed me to use single SVG icons as an input, and then, with the use of few Grunt tasks, to generate an SCSS file containing all of the icons as data URIs.
%icon { &::before { background-size: cover; display: inline-block; content: ""; } } @mixin icon () { &::before { background-size: cover; display: inline-block; content: ""; } } [class^="icon-"], [class^="i-"], [class*=" i-"] { @include icon(); } .icon-arrow-down::before { background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml...%201.522%2016%201.522zm0%2020.396l4.008-4.085-.963-.948-2.33%202.237v-8.155h-1.507v8.155l-2.33-2.237-.963.948L16%2021.918z%22%2F%3E%3C%2Fsvg%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; width: 1em; height: 1em; }
For color setting I used simple SASS placeholders which change the vertical background position. The most frustrating part was figuring out round percentages for the browsers, to avoid rounding up or down the pixel values with decimal places. The problem was how background-position property behaves when percentage is used. You can find good explanation written by Chris Coyer here.
The equation to come up with the % step is `100%/(n-1)`, where n is the number of images in the sprite. I ended up creating an 11th color version so the numbers were perfect.
For those worried about the file size: it’s not an issue since gzip compression handles it amazingly well – all of the paths for different colors are exactly the same, the only thing that changes is the fill color value, thus allowing the compression to work its magic.
%blue { &::before { background-position: 0 10%; } } %red { &::before { background-position: 0 20%; } }
If you don’t want to use SASS, you could pretty easily replace placeholders with normal classes. Just watch out to avoid overlapping with some existing ones that might exist in some places as helpers. This can of course be solved pretty easily by adding a prefix.
Implementation
I included the output SCSS file in our project using Bower. Then I imported it to one of our stylesheets by a simple `@import` statement.
Now to use it with existing elements I needed simply to extend the classes in project’s stylesheets.
.icon-project-specific-name { @extend .icon-search; }
For the elements with class names not matching `[class^=”icon-“]` I just needed to add `@extend %icon`.
Here’s an example of the complete implementation of a white category icon with a label inside the element:
<div class=”category-label category-news”>News</div> <style> .category-news { @extend %icon; @extend .icon-news; @extend %white; } </style>
Note about media queries
Since you can’t really use placeholders in media queries, and sometimes it’s necessary to change the icon on different devices, I added mixins alongside the classes for all of the icons. So instead of `@extend .icon-news` you can just use `@include .icon-news`.
Overview
The proposed solution takes care of a couple of things. Firstly, it automatizes the entire process of adding new icons and deploying it (via Travis) to the CDN. Secondly, it allows for quick migration of existing icon implementations in our projects without changing the HTML. Lastly, we have all the benefits of using SVG images starting with easier sizing (with `backgorund-size: cover` it’s pretty easy) up to using different colors in one icon.
The main drawback is that you have to use SASS in your projects if you want to take full advantage of the placeholders or use icons in classes with custom names (without any `icon-` prefix).
You can find the whole Grunt config along with an example in this repo. Keep in mind that it’s a rather rough proof of concept. Feel free to use or modify it to your needs. It includes a couple of intermediary steps to help with file conversions, sprite creation and handling of the different color versions (configured in a separate file).