At SassConf I presented a one hour “featured workshop” on lists, while also touching on new Sass 3.3 features such as maps and string functions. After all, the best way to learn how new functionality works is to toy around with it (and break stuff), so I decided to create an “animation trajectory grid” (or ascii art, if you will) and transform that into a CSS animation.
The finished animation looks like this (webkit only).
The trajectory grid is a list of 10 items, each consisting of 40 characters. UTF8 arrows indicate where our rocket image should move to, and how it should rotate.
1 2 3 4 5 6 7 8 9 10 11 12 |
$grid: ( "↓ ", " ↘ ", " ↓ ", " ↘ ", " → ", " → ", " ↘ ", " → ", " ↑ ", " ↑ " ); |
This is the only configuration our Sass code needs to work out the CSS, and you can add rows and columns as you please (as long as all lines have the same amount of columns). If you want to toy around with the full source, you can get it at GitHub. Please read the instructions: you will need Sass 3.3 for this to work. This repository includes more fun examples, will write about those later. For now, let’s dissect the Sass code!
First, we will need to set some variables, so we know how many rows and columns we’re dealing with:
1 2 3 |
$grid-width: str-length(nth($grid, 1)); $column-width: 100% / $grid-width; $row-height: 100% / length($grid); |
On line 1 we’re already taking advantage of one of the new string functions in Sass 3.3. We need to know the number of columns in our grid, and assuming all lines are of equal length, we’re calling str-length()
on the first line in the list. We use the nth()
function to get to that first line: the first parameter holds the list, and the second parameter tells it which line we want to be returned (please note: indexes in Sass start at 1, not 0).
Our animation is going to be fluid. All columns are spread out evenly over the available width of the page, so we need to use percentage values for all columns. There’s some basic math going on at line 2, where we divide 100% by the number of columns. We have 40 columns, so the width of a column will be 100% / 40 = 2.5%.
On line 3, we do about the same for the height of each row. We want to use a total height of 100% and divide that by the numbers of lines in our list. The length()
function gives us the number of items in our list.
Before getting started on the actual animation, let’s first set up the rocket image:
1 2 3 4 5 6 7 8 9 |
#rocket { position: absolute; margin: -87.5px 0 0 -50px; -webkit-transform: rotate(180deg); -webkit-animation-name: fly; -webkit-animation-duration: 5s; -webkit-animation-timing-function: linear; -webkit-animation-fill-mode: forwards; } |
There’s just some plain CSS going on here. The -webkit-transform: rotate(180deg)
is in there just to make the rocket point downward by default (it’s an upward pointing image). The fly
animation name doesn’t have any meaning just yet: we’re going to give our keyframes that name right now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@-webkit-keyframes fly { $i: 1; @each $row in $grid { $char: index-of-char(nth($row, 1)); #{$row-height * $i} { top: $row-height * $i; left: $column-width * nth($char, 1); -webkit-transform: rotate(#{rotation-of-char(nth($char, 2))}deg) } $i: $i + 1; } } |
This basically is where the magic happens. We’re setting up our keyframes, naming them fly
, and use @each
to loop through all lines in our grid.
Next we need to know which character on the line we’re looking at appears at which position. For this we use a custom function called index-of-char()
:
1 2 3 4 5 6 7 8 9 10 11 |
@function index-of-char($string) { @for $i from 1 through $grid-width { $char: str-slice($string, $i, $i); @if $char != ' ' { @return ($i, $char); } } @return (0, 0); } |
This function takes a string as its only argument, and the string we’re supplying to it is a line from our grid. Using @for
we’re going over each character in the string by calling the new str-slice()
function 40 times (the number of columns). This function takes three arguments: a string, the index to start at and the index to stop at. By using the same value for both start and stop, it will return the character at that exact position. We then simply check if that character isn’t a string, optimistically assuming that all non-string characters have to be arrows. If so, we return a list (hence the ()
) with two values: position and the arrow character. We need the character to determine the rotation later on.
Let’s get back to our previous code sample, the one with the keyframes. After calling our custom function, $char
now holds the list with position and character. Next we’ll output the actual CSS for our animation.
#{$row-height * $i}
sets the percentage at which an animation will occur. Earlier we determined that the row height was 10% (100% / the number of lines in our grid). With $i
going from 1 to 10 (again, the number of items in our grid), this will output 10%
for the first iteration, 20%
for the second, etc. Our top
value is going to be the same.
To determine at which percentage from the left our rocket should appear, we multiply $column-width
(2.5%, remember?) with the position our index-of-char()
function returned. Because that returned a list, we need to use nth()
to get the first value.
Next we need to know the degrees of rotation, based on the arrow character that’s in our $char
list as well. For this we’re using another custom function, rotation-of-char()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@function rotation-of-char($string) { $arrows: ( ↑: 0, ↗: 45, →: 90, ↘: 135, ↓: 180, ↙: 225, ←: 270, ↖: 315 ); @return map-get($arrows, $string); } |
We supply this function with the second item from our $char
list, which will be an arrow string. By using a map (another new Sass 3.3 feature) we can look up the key ↓
using the map-get()
function and return its value. In this case, that’s 180 degrees. We then use simple interpolation to insert that into -webkit-transform: rotate()
value.
After incrementing $i
for the next line in the grid, we’re done. Sass compiles to this, and we have created an animation based entirely on a piece of ascii art!