HTML5 Zone is brought to you in partnership with:

Karl is a geek who loves to play with all sorts of frameworks and languages, and has a nice monitor tan to show for it. Karl is a DZone MVB and is not an employee of DZone and has posted 6 posts at DZone. You can read more from them at their website. View Full User Profile

Easy CSS Management and Spriting With Sass and Compass

03.04.2013
| 2392 views |
  • submit to reddit

Every so often I like to revisit old projects of mine and refactor them a little, either using some new ideas I learnt in the meantime, or trying out some new technique or framework on familiar ground. This time, I’m going over a wordpress theme I made a couple of years ago and using Compass to organize the style sheets. Converting the existing style sheet (circa 8k uncompressed) to SCSS didn’t take long – only a few minutes by hand – and there are tools which you can use to automate the conversion, so it left me plenty of time to explore more Sass features which make style sheet management much easier. Today we’re going to look at command directives and a few basic spriting helpers.

What are Sass and Compass?

Sass (Syntactically Awesome Style Sheets) is a language built around and on top of CSS3, adding a bunch of features like variables, mixins (sort of macros), and several helpers for more common css operations. It can be expressed in two forms of syntax – Sass (which uses indentation to define code blocks) or Scss (which uses braces). I’ll be using the scss form for these examples, but in either case, anyone familiar with css will be at home. Both forms compile to css files.

Compass is a CSS authoring framework which adds even more tools on top of the Sass spec; including spriting and typography. The added value makes it worth looking into even if you’re just starting out with scss.

The examples shown below can be downloaded here.

Variables

Ever had to go through several lines (or files) of css to replace the same colour code over and over again? I hate having to do that – always end up missing one or changing one I shouldn’t have. Sass fixes that by allowing us to define variables. This is great, as (given proper naming) we only need to change one value when someone decides that the header is the wrong shade of {{ some bizarrely named colour I won’t even pretend to understand }}. Since Sass also supports mathematical operations we can use it to define values in relation to each other; for example, making the line height a function of the font-size:

$copy-font-size: 10pt;
article {
    p {
        font-size: $copy-font-size;
        line-height: $copy-font-size * 1.2;
    }
}

Which compiles to

article p {
    font-size: 10pt;
    line-height: 12pt;
}

Changing the font size (or even the units of the font size) will cause the line height to maintain the same relationship.

Control Directives: @for, @each and @if

There are always a few situations where we need to define styles which are almost identical. In some cases, we can express them as a function of something; for example, the font-size of a header element will be inversely proportional to its rank, or an icon will be one of a predefined set where only the background-image changes. Sass lets us set up some iterations, so we can be a bit lazier.

In this section, I’ll talk about three of these directives: ‘@for’, ‘@each’ and ‘@if’. There is also a ‘@while’ directive, but I will leave that out as I don’t particularly like it. It’s only really useful if you have complex conditions (if so: why do you have complex conditions), and you can easily hang your compilation if you create a situation where the exit condition is never met.

@for .. through and @for .. to

This is a common or garden for loop, the likes of which can be found in practically every language. It takes a start and end value and iterates between them. If the ‘through’ keyword is used, the end value is included; if ‘to’ is used, then it is not. So:

$cellWidth: 60px;
$cellGutter: 20px;

@for $i from 1 through 12 {
    .span#{$i} {
        width: $cellWidth * $i;
        margin-right: $cellGutter;
    }
}

Generates 12 items

.span1 { width: 60px; margin-right: 20px; }
.span2 { width: 120px; margin-right: 20px; }
.span3 { width: 180px; margin-right: 20px; }
.span4 { width: 240px; margin-right: 20px; }
.span5 { width: 300px; margin-right: 20px; }
.span6 { width: 360px; margin-right: 20px; }
.span7 { width: 420px; margin-right: 20px; }
.span8 { width: 480px; margin-right: 20px; }
.span9 { width: 540px; margin-right: 20px; }
.span10 { width: 600px; margin-right: 20px; }
.span11 { width: 660px; margin-right: 20px; }
.span12 { width: 720px; margin-right: 20px; }

On the other hand

@for $i from 1 to 7 {
    h#{$i} {
        font-size: 12pt + ((6 - $i) * 6);
        @if ($i < 2) { color: red; }
    }
}

Will generate 6

h1 {
    font-size: 42pt;
    color: red;
}
h2 { font-size: 36pt; }
h3 { font-size: 30pt; }
h4 { font-size: 24pt; }
h5 { font-size: 18pt; }
h6 { font-size: 12pt; }

Above you can also see the if condition in action, which works exactly like you would expect it to. You may also have noticed that the index value, $i, is occasionally wrapped, as in h#{$i}. This lets Sass know that I want to see the value of $i, rather than very ugly selector or some wierd piece of css. You only need to do this if the value is mixed with plain css.

@each

The each directive loops over a list of values – useful if you’re working with a distinct collection.

.icon {
    width:16px;
    height:16px;
}
@each $icon in add, attach, bell, star {
    [data-icon=#{$icon}] .icon { 
        background-image: url(img/#{$icon}.png); 
    }
}

produces

.icon {
    width: 16px;
    height: 16px;
}

[data-icon=add] .icon { background-image: url(img/add.png); }
[data-icon=attach] .icon { background-image: url(img/attach.png); }
[data-icon=bell] .icon { background-image: url(img/bell.png); }
[data-icon=star] .icon { background-image: url(img/star.png); }

Sass also defines a few list functions to help you complicate your scripts to your liking.

Spriting

Image sprites are a handy way of reducing page load times, especially if you have a lot of small images in your page or web application. On the down side, it can take you ages to line up the sprites and get the background positions right. Well, not any more: Compass provides some beautifully straightforward spriting tools. To create a sprite sheet, just drop all your images in a folder, and import them into your scss:

@import "icons/silk/*.png";
@include all-silk-sprites;

In my case, I’m using four icons from FamFamFam’s Silk Icon Set, which I left in a folder called silk. This generates

.silk-sprite, .silk-add, .silk-attach, .silk-bell, .silk-star {
    background: url('/images/icons/silk-sf90b8edda1.png') no-repeat;
}

.silk-add { background-position: 0 -16px; }
.silk-attach { background-position: 0 -48px; }
.silk-bell { background-position: 0 0; }
.silk-star { background-position: 0 -32px; }

Compass also generates a few helper functions, like ‘all-silk-sprites’. The name of the functions is based on the name of the folder the icons are in – it always uses the name of the last folder before the file selector, in this case, silk.

Customizing spriting selectors

I wanted a bit more control over the generated selectors and styles, and it turns out that Compass delivers quite easily. By using the spriting functions, we get a great deal more control:

@import "compass/utilities/sprites/base";
$icons: sprite-map("icons/silk/*.png");

.icon {
@include sprite-dimensions($icons, nth(sprite-names($icons),1));
    background: $icons no-repeat;
}

@each $icon in sprite-names($icons) {
    [data-icon=#{$icon}] .icon {
        @include sprite-background-position($icons, $icon);
    }
}

This generates

.icon {
    height: 16px;
    width: 16px;
    background: url('/images/icons/silk-s95fd478241.png') no-repeat;
}

[data-icon=add] .icon { background-position: 0 -16px; }
[data-icon=attach] .icon { background-position: 0 -48px; }
[data-icon=bell] .icon { background-position: 0 0; }
[data-icon=star] .icon { background-position: 0 -32px; }

Not only can we define classes any way we like, but now, adding an icon is as simple as dropping a png into the silk folder, and recompile the scss. The script will take care of the rest for us. This one’s a bit heftier than the others, so let’s have a look at it piece by piece.

@import “compass/utilities/sprites/base”;

Here we’re loading in the sprite functions. Compass does not load them in by default, so you’ll need to do so manually.

$icons: sprite-map(“icons/silk/*.png”);

We create the sprite map instance and keep a reference to it. We’ll need this to access sprite functions soon.

.icon {
@include sprite-dimensions($icons, nth(sprite-names($icons),1));
background: $icons no-repeat;
}

The first part gets the width and the height of the first sprite in the map – we’re assuming that all the sprites in the map are the same size, which is true here, but may not always be the case. The sprite-names function gives us the names of all the sprites in the map, while nth is used to access an element by index – in this case, the first one.

The second part dumps out the path of the generated style sheet.

@each $icon in sprite-names($icons) {
[data-icon=#{$icon}] .icon {
@include sprite-background-position($icons, $icon);
}
}

Here we’re using the sprite names-function again, this time iterating over it using the @each directive as we did earlier. We use the icon name as we did in the example for @each, creating the selector, and then we use it in sprite-background-position to derive the offsets for the sprite.

Other stuff

This post covers a tiny fraction of a huge number of features – the possibilities for streamlining your css generation are great. The little I’ve seen so far has encouraged me to keep digging, and it’s very likely that Compass will become a permanent resident of my toolkit – it’s just so much better than hammering out css by hand.

Published at DZone with permission of Karl Agius, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)