CSS animations using SCSS mixin, interpolation & loops
CSS3 animations are now supported by all modern browsers. It is very common to use CSS instead of JavaScript for animations. (Woohoo! Less lagging on the page!)
SCSS is extremely helpful for a large amount of animations. Imagine, you have to create a page with 20 similar animations. How many divs do you need? How many keyframes do you need? They have different animation-duration and different number of “frames”. Creating 20 similar animations means to create 20 sets of keyframes and divs. It’s at least a few hundred lines of code if you do not include @-webkit-keyframes, @-moz-keyframes or @-o-keyframes, are you going to spend several days writing the .css for these animations?
SCSS to the rescue!
Recently I am working on a game with 5 characters. Since there will be 4 levels in the result, there will be 5x4 character animations.
My DOM looks like this:
<div class=”expert”>
<h1>expert</h1>
<div class=”result-animal expert_bear”></div>
<div class=”result-animal expert_bunny”></div>
<div class=”result-animal expert_cat”></div>
<div class=”result-animal expert_penguin”></div>
<div class=”result-animal expert_reindeer”></div>
</div>
<div class=”advanced”>
<h1>advanced</h1>
<div class=”result-animal advanced_bear”></div>
<div class=”result-animal advanced_bunny”></div>
<div class=”result-animal advanced_cat”></div>
<div class=”result-animal advanced_penguin”></div>
<div class=”result-animal advanced_reindeer”></div>
</div>
<div class=”intermediate”>
<h1>intermediate</h1>
<div class=”result-animal intermediate_bear”></div>
<div class=”result-animal intermediate_bunny”></div>
<div class=”result-animal intermediate_cat”></div>
<div class=”result-animal intermediate_penguin”></div>
<div class=”result-animal intermediate_reindeer”></div>
</div>
<div class=”novice”>
<h1>novice</h1>
<div class=”result-animal novice_bear”></div>
<div class=”result-animal novice_bunny”></div>
<div class=”result-animal novice_cat”></div>
<div class=”result-animal novice_penguin”></div>
<div class=”result-animal novice_reindeer”></div>
</div>I used Pug to generate the DOM. I won’t be talking about Pug in this article. If you are interested, take a look here: https://pugjs.org/
each lvl in ['expert', 'advanced', 'intermediate', 'novice']
div(class=lvl)
h1 #{lvl}
each val in ['bear', 'bunny', 'cat', 'penguin', 'reindeer']
div.result-animal(class=lvl+'_'+val)I decided to use keyframes and background-image to do the animations because it’s the easiest way for me to change the image of any frame if the designer needs to modify it. It is also easier for me and the designer to fine-tune the duration of each frame.
To kick start the CSS-writing process, I started with listing out the variables: the levels, and the animals.
$levels: “advanced” “expert” “intermediate” “novice”;
$animals: “bear” “bunny” “cat” “penguin” “reindeer”;These 2 lists would be looped over later on.
There are 2 kinds of loops that I use very often from SCSS:
@for $i from 1 through 10 {}
@each $i in $list {}As I am using lists this time, I would be using the @each loop. I have 5 characters for each level. So the basic setup would be:
@each $l in $levels {
@each $a in $animals {
}
}For each level, I need to create a specific div with the line stating the animation and a different height, so I used a mixin:
@mixin create-animal($l, $a, $h, $t) {
.#{$l}_#{$a} {
height: $h;
animation: #{$a}_#{$l} $t infinite;
}
}In the @each loops, we need the variables for height and animation-duration, so we put these:
@each $l in $levels {
@each $a in $animals {
$t: 0.5s;
$h: 319px;
@include create-animal($l, $a, $h, $t);
}
}As I said, the characters should have different height, so I used an SCSS map (similar to object in JavaScript):
@each $l in $levels {
@each $a in $animals {
$t: 0.5s;
$hs: (
bunny: 179px,
penguin: 209px,
cat: 219px,
reindeer: 319px,
bear: 279px
);
$h: map-get($hs, $a);
@include create-animal($l, $a, $h, $t);
}
}So now it’s time to create the keyframes:
@each $l in $levels {
@each $a in $animals {
$t: 0.5s;
$end: 8;
$hs: (
bunny: 179px,
penguin: 209px,
cat: 219px,
reindeer: 319px,
bear: 279px
);
$h: map-get($hs, $a);
@include create-animal($l, $a, $h, $t);
@keyframes #{$a}_#{$l} {
@for $i from 0 to $end {
#{100/$end*$i + "%"} {
background-image: url(https://www.efcampaigns.com/campaign/winterchallenge/dist/images/result/#{$l}/#{$a}_#{$l}_#{$i}.png);
}
}
}
}
}The $end variable is the total number of frames (background-images). 100/$end*$i + “%" gives me the % for setting each frame (given that each frame lasts for an equal duration).
However, this is still one-step until the finished SCSS because there is one character with a different animation-duration and a different number of frames. And each animal has a different animation-duration and a different number of frames for one of the levels, so I need 1 if and 1 more list.
@each $l in $levels {
@each $a in $animals {
$t: 0.5s;
$end: 8;
$hs: (
bunny: 179px,
penguin: 209px,
cat: 219px,
reindeer: 319px,
bear: 279px
);
$h: map-get($hs, $a);
@if ($a == 'bear') {
$t: 0.55s;
$end: 10;
}
$n_ani: (
bunny: (1.2s, 11),
penguin: (0.6s, 7),
cat: (1.2s, 11),
reindeer: (0.5s, 4),
bear: (1s, 8)
);
@if ($l == 'novice') {
$t: nth(map-get($n_ani, $a), 1);
$end: nth(map-get($n_ani, $a), 2);
}
@include create-animal($l, $a, $h, $t);
@keyframes #{$a}_#{$l} {
@for $i from 0 to $end {
#{100/$end*$i + "%"} {
background-image: url(https://www.efcampaigns.com/campaign/winterchallenge/dist/images/result/#{$l}/#{$a}_#{$l}_#{$i}.png);
}
}
}
}
}Here’s the final SCSS:
$levels: "advanced" "expert" "intermediate" "novice";
$animals: "bear" "bunny" "cat" "penguin" "reindeer";@mixin create-animal($l, $a, $h, $t) {
.#{$l}_#{$a} {
height: $h;
background-image: url(https://www.efcampaigns.com/campaign/winterchallenge/dist/images/result/#{$l}/#{$a}_#{$l}_0.png);
animation: #{$a}_#{$l} $t infinite;
}
}@each $l in $levels {
@each $a in $animals {
$t: 0.5s;
$end: 8;
$hs: (
bunny: 179px,
penguin: 209px,
cat: 219px,
reindeer: 319px,
bear: 279px
);
$h: map-get($hs, $a);
@if ($a == 'bear') {
$t: 0.55s;
$end: 10;
}
$n_ani: (
bunny: (1.2s, 11),
penguin: (0.6s, 7),
cat: (1.2s, 11),
reindeer: (0.5s, 4),
bear: (1s, 8)
);
@if ($l == 'novice') {
$t: nth(map-get($n_ani, $a), 1);
$end: nth(map-get($n_ani, $a), 2);
}
@include create-animal($l, $a, $h, $t);
@keyframes #{$a}_#{$l} {
@for $i from 0 to $end {
#{100/$end*$i + "%"} {
background-image: url(https://www.efcampaigns.com/campaign/winterchallenge/dist/images/result/#{$l}/#{$a}_#{$l}_#{$i}.png);
}
}
}
}
}
If you like this article, please click “Recommend” and follow me! :)
Have fun with SCSS!
For detailed use of SCSS, please refer to the official documentation:
http://sass-lang.com/documentation/file.SASS_REFERENCE.html
