Sass is a powerful and flexible CSS pre-processor that we use extensively at Phuse. Using Sass features like nested selectors, mixins and extends has become second nature to us as front-end developers, but what if you want to get a little bit fancier? Some of the most powerful features of Sass are SassScript and Control Directives, which give you some basic scripting functionality in your stylesheets.
The following is a short example of how we can use SassScript lists and the @for
directive to make working with image sprites a lot easier.
An example of Sass scripting: image sprites
In this example from a recent project, I need to be able to add the appropriate flag icon as a background image to HTML elements with a class structure of country-#{country_name}
. Since there are going to be at least about 20 countries in the final application, It makes sense to save HTTP requests by making an image sprite.
Making the sprite
I’m going to make my sprite image in Photoshop because it’s what I know, but you could easily use another image editing program.
The designer I worked with on this project found a set of flag icons he liked, and uploaded it to a shared folder so I could access it. The widest flag icon is 16px wide and all the icons were exactly 11px tall, so let’s make a new document that measures 16 x the number of icons I have (19) by 11px tall.
Using the very handy (and free) Photoshop extension GuideGuide, we can set up a grid every 16px.
I have a folder containing 19 flag icons named by country. Selecting all of the icon files in the Finder and dragging them onto my canvas in Photoshop imports the images as Smart Objects, and I’m prompted to place them all one at a time, in alphabetical order. Since we’ve set up guides every 16px, it’s easy to align the icons to the grid.
Once the sprite is ready, save it as a .png file somewhere where you can reference it from your stylesheet.
Setting up our Sass
Now it’s time to move on to our Sass file. I’m using the .scss syntax variety. The plan for generating code for our sprites is to store a list of all the countries that we want to create icons for, and then assign our sprite image as a background-image
for all of our icons, then dynamically calculate the offset for the background-position
of each flag icon.
Let’s set up some basic styles for our flag icons. All the flags will have a class of .ico-flag
. The following adds a pseudo-element before our .ico-flag
element, and adds our flags sprite as a background image.
.ico-flag {
position: relative;
height: 11px;
&:before {
content: "";
position: absolute;
width: 14px;
height: 11px;
top: 50%;
left: 0;
margin-top: -5px;
background-image: image-url("sprite-flags.png");
background-repeat: no-repeat;
}
}
Notice how there’s no background-position
property yet. This is the part we're going to generate from a list of countries.
Creating a list in Sass
Next, we need a list of country names to iterate over. To create the list, we could type out all the country names manually, but I wrote a simple (and probably awful) bash script to grab the country names from the file names, remove the file extension, convert all characters to lowercase, and replace dashes with underscores:
#!/bin/bash
cd folder/with/icons
for string in `ls`
do
echo ${string%%.*} | tr '[:upper:]' '[:lower:]' | sed 's/-/_'
done
This gives us the list of countries in the order they appear in our sprite. Let’s create a list in our .scss file to store these values:
$countries: australia brazil canada chile costa_rica croatia greece india iran italy mauritania poland russia rwanda south_africa spain united_states zimbabwe;
Iterating over a list
If we just needed to work with the value of each list item, we could use the @each
directive:
@each $country in $countries {
//do something with $country
}
But the outcome we’re trying to achieve is a dynamically-generated background position, based on the width of each icon (16px), and the position of the country in the list. Therefore, we’re going to also need the index value for each item in the list. To iterate over a list with an index, we can use the Sass @for
directive:
@for $i from 1 through length($countries) {
}
This gives us access to an index value for each element in our list. Since we also want the name of the country (for a dynamically generated class name), we’ll use nth()
to grab the country name at the specified index, and then write a selector for that country:
@for $i from 1 through length($countries) {
$country: nth($countries, $i);
.ico-flag-#{$country} {
}
}
Setting up our @mixin
To calculate the background-position for our icons, we’re going to create a @mixin
. We could also just do the calcualtions in the body of our @for
statement, but I'm making a mixin so that I can use this elsewhere in my scss if I need to (for example, maybe somewhere down the line I need to use the "China" flag to indicate that something is in Chinese).
@mixin flag-sprite($offset) {
$offset: $offset - 1;
&:before {
background-position: 16px*-$offset top;
}
}
This mixin takes one argument, the offset, which is the number of grid units the icon is away from the leftmost edge of the sprite. Since our list starts at 1, and our first sprite needs to be at 0, we’ll have to adjust the $offset
value by 1 for each item.
Putting it all together
So if we now reference our flag-sprite
mixin from our @for
loop over our countries, we have something that looks like this:
@for $i from 1 through length($countries) {
$country: nth($countries, $i);
.ico-flag-#{$country} {
@include flag-sprite($i);
}
}
The compiled CSS from our Sass looks something like this:
.ico-flag-australia:before {
background-position: 0px top;
}
.ico-flag-brazil:before {
background-position: -16px top;
}
.ico-flag-canada:before {
background-position: -32px top;
}
.ico-flag-chile:before {
background-position: -48px top;
}
/*etc*/
Here’s how the demo looks:
Overall, the CSS output is about 70 lines long, compared to the 15 lines we just wrote in Sass. Obviously, since Sass is a precompiler, all 70 lines get sent to the browser (though hopefully less because you’ve minified!) but it sure is a lot easier to read, and saved us a lot of time manually typing out background-positions!
This method is also a lot less brittle than manually typing them out: if we add another country somewhere down the line, we only have to edit one line of scss (adding the country name in the appropriate spot on the list).
This is just a small example of the options opened up by Sass scripting, and there’s even more to come with Sass 3.3.