Sprites are a pain, @each makes it hurt less

Sprites are a pain in the ass, we all know that. And with a recent reminder of Google's Bootstrap I am once again reminded of the horrible ways that people solve problems.

It's funny to me. We are reminded again and again that universal selectors are bad. We as UI devs are beat over the head time and time again that CSS performance is key. As shown in the wildly popular deck by Jon Rohan, unqualified selectors are only second in the list of no-no's behind universal selectors. But yet we see them all over the place. Do what we say, but not as we do is the world we live in.

In Google's Bootstrap we see the following icon code, [class^="icon-"], [class*=" icon-"] is the selector? Really?

[class^="icon-"], [class*=" icon-"] { <-- WTF?!
display: inline-block;
width: 14px;
height: 14px;
margin-top: 1px;
line-height: 14px;
vertical-align: text-top;
background-image: url("../img/glyphicons-halflings.png");
background-position: 14px 14px;
background-repeat: no-repeat;

To me this is the lazy man's answer to the problem of using icons and sprites. Writing good selectors take effort and time. Are we too lazy to do the work required to achieve the performance zen that we are looking for?

Then we are also left with lines of REALLY ANNOYING code like this.

icon-music {
  background-position: -24px 0;}

.icon-search {
  background-position: -48px 0;}

.icon-envelope {
  background-position: -72px 0;}   <-- pattern, huh?

There is a better way

Correct me if I am wrong, but Google's Bootstrap, just like Twitter's Bootstrap is using LESS. But I can't find any evidence of using LESS to make this 'less' painful (ha, see what I did there?).

Here is a really kick Sass example for how we can use some of Sass' power features to make this less painful. We can also write better classes that don't break performance rules and fits the popular mindset of OOCSS.

The following mixin will pass in the image file name to create a semantically named block with an <a> tag to apply the nested CSS. We can also pass in a value for the $square variable that will be used for the height, width and line-height.

=social-icons($file, $ico-class, $square)
  .#{$ico-class} a
    display: inline-block
    width: em($square)
    height: em($square)
    line-height: em($square)
    background: url("#{$imgDir}#{$file}.png") 0 0 no-repeat

Next we need Sass to write out all the classes and background offsets for each icon. Leaving the developer with only having to maintain a single list of icons. Notice that these classes are not prefixed by the parent class. They will be perfectly reusable OOCSS class throughout your site.

@each $icon in $icons   <-- @each will cycle through each object in the list of icons
  .#{$icon}   <-- in each loop will apply an object name to this variable
    a
      &:after
        content: '#{$icon}'  <-- OMG, write the icon name into the CSS

This is getting pretty awesome, but there are still some missing parts. Where do we get the list of icons from? For this we go back to the mixin to add $icons to the list of arguments.

=social-icons($file, $ico-class, $square, $icons)

Now that we have the file name, the size of the square and the list of icons, we need to write out the offset values. To do this we need to add a bit more logic.

We need to establish some X and Y positions, convert those values into em, apply that to a background-position and last, use an iterator to offset the values. Wow, that's a mouthful. Checkout the following example.

First we establish the X and Y positions and the offset with default values in the mixin arguments.

=social-icons($file, $ico-class, $square, $icons, $x: 0, $y: 0, $offset: 14)

Then we convert those values to em using the function and assigning that value to a new variable.

$ypos: em($y)
$xpos: em($x)

The last thing we need to do is update that @each function. Here we add the background position with an iterator function that takes the current value of $ypos and update it with $ypos - em($offset). How bad ass is that?

@each $icon in $icons
  .#{$icon}
    a
      background-position: $xpos $ypos 
      $ypos: $ypos - em($offset)        <-- Iterator function
      &:after
        content: '#{$icon}' 

Just use it!

This is all worthless if you can't use it. In your Sass file, you simply need to add this line.

+social-icons(social-media-icons, the-icons, 14, sassmeister twitter github)

There is one more thing we can do to make this even easier to manage. The list of icons will get hard to manage in this mixin as we get close to the 140 that Google Bootstrap supports. The next kick Sass thing we can do is take that list of icons and make that a variable, and pass that into the mixin like so.

// place this somewhere it is easy to manage
$the-icon-list: sassmeister twitter github

// place this anywhere in your Sass
+social-icons(social-media-icons, the-icons, 14, $the-icon-list)

Doing this will make managing the $the-icon-list much easier.

One more thing …

How can we use this to create additional semantic blocks that will support the icons? We do not want to reuse this mixin again because that will duplicate all the code. This is where Sass' @extend feature comes to the rescue. In our example we named the first block to contain the icons .the-icons. When we need to create another block that will contain more icons, we simply extend that as shown in the following example.

.new-ico-block
  @extend .the-icons

Let's see the whole thing

That was the walk through, here is the whole thing in it's glory!

=social-icons($file, $ico-class, $square, $icons, $x: 0, $y: 0, $offset: 14)
 // Using a parent class to apply styles to the `a` selector
 .#{$ico-class} a
   display: inline-block
   width: em($square)
   height: em($square)
   line-height: em($square)
   background: url("#{$imgDir}#{$file}.png") 0 0 no-repeat

 // Set your initial x/y-positions
 $ypos: em($y)
 $xpos: em($x)

 // The following function will loop through icons names in the mixin
 @each $icon in $icons
   .#{$icon}
     a
       background-position: $xpos $ypos 
       $ypos: $ypos - em($offset)
       &:after
         content: '#{$icon}'


// Using the mixin in your Sass
$the-icon-list: sassmeister twitter github
+social-icons(social-media-icons, the-icons, 14, $the-icon-list)


// Extending the selector for additional uses
.new-ico-block
 @extend .the-icons

Then here is the CSS output

.the-icons a, .new-ico-block a {
  display: inline-block;
  width: 1.16667em;
  height: 1.16667em;
  line-height: 1.16667em;
  background: url("/images/social-media-icons.png") 0 0 no-repeat; }

.sassmeister a {
  background-position: 0em 0em; }
  .sassmeister a:after {
    content: "sassmeister"; }

.twitter a {
  background-position: 0em -1.16667em; }
  .twitter a:after {
    content: "twitter"; }

.github a {
  background-position: 0em -2.33333em; }
  .github a:after {
    content: "github"; }

SassMeister.com

What good is code if you can't play with it? See living SassMeister gist


more rants