Writing on the principles & practice of building digital products

Animation with Bourbon & Sass

Previously the preserve of Javascript, using CSS for simple animation sequences is quicker, more modular and easier to frankenstein to do your bidding.

As our Animators are fond of telling us, lots of things moving at the same time feels unnatural. If you've got several elements moving into place, they should really do so in stages. While previously this would have been an effect only achievable with Javascript, in these days of wider animation support CSS-only options are yours for the taking.

I've used ‘staged’ animation (i.e. a series of animations which run in sequence) in a few places on this site, and for the first time I've pretty much exclusively used CSS to achieve it. Up until now my animation library of choice has been Transit, and while Transit is an excellent way of creating complex cross browser animation, when you only need to achieve a small amount of movement it can be overkill.

So without further ado, here's the goal - a series of social icons fading in and panning down:

Animation Demo

To make this effect we need three things:

  1. The animation keyframes which define the animation
  2. An animation mixin which applies the animation
  3. A loop to sequence the animations

Note: I'm using the Bourbon Sass mixin library as it helps keep our css free of browser prefixes. You can achieve the same with vanilla CSS, it would just be a lot more verbose.

Firstly, let's have a look at the keyframes:

@include keyframes(fadeIn) {
  from {
    @include transform(translateY(-20px));
    opacity: 0;
  }
  to {
    @include transform(translateY(0));
    opacity: 1;
  }
}

These keyframes do 2 things: offset the element to the top of where it's final position (using translateY) and fade the element in using opacity.

Next, let's look at how we'll apply these keyframes to an element using an animation mixin:

@mixin animation--fadedown($delay) {
  // Apply the fadeIn keyframes. Each animation 
  // will take 0.3s and have an ease-in-out
  @include animation(fadeIn 0.3s ease-in-out);
  // This animation should only play once
  @include animation-iteration-count(1);
  // Make sure the element maintains it's 
  // final visual state (i.e. 100% opacity)
  @include animation-fill-mode(forwards);
  // Delay - don't start the animation until we say so
  @include animation-delay(#{$delay}s);
}

As you can see from the comments, this mixin does a few things. But broadly speaking it makes the transition we've defined in the fadeIn keyframe usable. Now, let's take a look at how we can actually apply animation to our icons:

.nav--item {
  // Hide initially 
  opacity: 0;
  // Apply animation when the menu is active 
  .is--menu-active & {
    // Loop through each menu item 
    @for $i from 1 through 5 {
      // Apply animation using :nth-child pseudoclass
      &:nth-child(#{$i}) {
        // Include the animation mixin, together with 
        // a delay parameter.
        @include animation--fadedown($i/10);
      }
    }
  }
}

The idea here is to loop through the icons and apply the animation mixin to each one, in sequence, adding a delay parameter so they execute one after another. By using the $i variable we can specify a greater delay for the later icons in the menu.

The animation itself is triggered by applying the .is--menu-active class to a parent element of .nav--item (e.g. the body).

Want to make the delay between the animations longer or shorter?Just alter to the number after $i/ in the animation--fadedown mixin. For example, if you wanted to make the animation slower you could change it to something like:

@include animation--fadedown($i/5);

Similarly if you wanted it to run faster:

@include animation--fadedown($i/20);

You can check out the effect in action on this site, and in the source code on github. But for reference, here's an overview of the parts involved.

// NOTE: include the bourbon library – bourbon.io
// ---
// Usage:
// <nav>
//   <a class="nav--item"></a>
//   <a class="nav--item"></a>
//   <a class="nav--item"></a>
//   <a class="nav--item"></a>
// </nav>
// 
// Animation is triggered by applying the .is--menu-active 
// class to a parent element of .nav--item (e.g. the body)


// Animation keyframes

@include keyframes(fadeIn) {
  from {
    @include transform(translateY(-20px));
    opacity: 0;
  }
  to {
    @include transform(translateY(0));
    opacity: 1;
  }
}

// Animation mixin

@mixin animation--fadedown($delay) {
  @include animation(fadeIn 0.3s ease-in-out);
  @include animation-iteration-count(1);
  @include animation-fill-mode(forwards);
  @include animation-delay(#{$delay}s);
}

// Nav item selector

.nav--item {
  opacity: 0;

  .is--menu-active & {

    @for $i from 1 through 5 {
      &:nth-child(#{$i}) {
        @include animation--fadedown($i/10);
      }
    }
  }
}