I learnt something new today while building Day #10 of my Web Design Advent Calendar (a little side-project in the lead-up to Christmas), so I thought I’d share my process.

For a while I’ve been intending to explore Sass maps, having read about lots of cool ways to utilise them, but I haven’t had much call to use them in my day-to-day work. My comfort zone is on the visual / design end of the CSS spectrum, and some of these map functions seemed to be getting a bit too much into programmer territory, and therefore a little intimidating!

Today in my Advent Calendar I wanted to add some flashing lights to my Christmas tree, and I decided this would be the perfect opportunity to bite the bullet and try out some Sass map magic! In the SVG, the lights on the tree are grouped according to colour. I could easily animate the colours to darken and lighten using CSS keyframe animation, starting with a darker shade of the base colour and flashing a lighter colour:

  .red {
    fill: darken(#EA385C, 20%);
    animation: .4s flash-red ease-in-out infinite;
}
@keyframes flash-red {
    40% { fill: darken(#EA385C, 0%);    }
    80% { fill: darken(#EA385C, 20%); }
}

However, this entails writing out a new keyframe animation for each colour. Furthermore, in this example all lights of a particular colour would flash simultaneously, whereas I want more of a random effect, using animation-delay to stagger the different lights.

This is where Sass maps come in handy. On my Christmas tree I have five different coloured lights, so I can create a map to define them:

  $colors: (
  red: #EA385C,
  gold-lt: #E7B75C,
  blue-lt: #386FB1,
    blue-dk: #28527C,
    gold-dk: #B28947
);

Then I can assign each group of lights a color (the ‘key’ in the map):

  .red {
    fill: map-get($colors, red);
}

To avoid too much repetition you can use a helper function. This Sitepoint article by Hugo Giraudel is a great resource. Even better, we can use the @each directive in conjunction with the map to loop through the colours and assign them to the corresponding class:

  @each $name, $color in $colors {
  .#{$name} {
    fill: $color;
  }
}

(More on that here.)

The most important thing I want to automate in this scenario is the keyframe animation, which will be the most repetitive component. I couldn’t find many resources on using keyframe animations with Sass maps, and I wasn’t sure if it would work. But it turns out putting the keyframe animation inside the same directive is fine!

In my animation I want to go from a darker variant to a lighter one and back again (as the lights are flashing on, rather than off), so I change the fill colour to start with the darker shade and add in my animation:

  @each $name, $color in $colors {
  .#{$name} {
    fill: darken($color, 20%);
        @keyframes #{$name}-flash {
            40% { fill: darken($color, 0%); }
            80% { fill: darken($color, 20%); }
        }
        animation: .6s #{$name}-flash ease-in-out infinite;
  }
}

So far so good, my lights are now flashing without having to write separate keyframe animations for each colour!

But remember, I want to stagger the animations so the lights flash in a more random fashion. I could go this by adding different classes to the lights in the SVG code and varying the animation-delay property for each:

  .g1 {
    animation-delay: .4s;
}
.g2 {
    animation-delay: .8s;
}

We could automate this too – which would certainly be useful if we had a large number of groups of lights that we wanted to flash at different points. Firstly, we’ll create another map with our delay times:

  $timings: (
    g1: 0s,
    g2: .4s,
    g3: .8s
);

Then we can use the @each directive again:

  @each $timing-name, $timing in $timings {
  .#{$timing-name} {
        animation-delay: $timing;
  }
}

And there we have our flashing Christmas tree!


972 1 12