Creating Repeatable CSS Animations with Sass

working animations gif

The problem: an HTML element cycles through one of four possible classes every time it is clicked, each giving a different background color to the element. We want the element to visually pulse when it shifts between states.

Easy enough, right? We’ll just attach a custom CSS animation to each of the four classes, ensuring that an animation fires every time the element changes. Here’s that approach in action (with animations lovingly taken from Animate.css):

So, that didn’t work! Unfortunately, the animation only runs a single time – on first click – and then never again. To understand why it failed, we need to first learn more about modelling a changing element with CSS animations.

CSS Animations

We can do much more complex things using CSS animations (right) but it is much easier to trigger CSS transitions based on state changes (left).

CSS animations allow you to control how an element is rendered to the DOM. At its core, an animation is made up of two parts:

  • A keyframe set, that declares how an element should be displayed at various stages of being rendered. They are constructed as a series of percentages. For instance, the following keyframe describes an animation named “stretch” that expands an element to 200px when it is 30% competed, and then returns the element’s width to 100px when it has finished.

    @keyframes stretch
      0%
        width: 100px
      30%
        width: 200px
      100%
        width: 100px
    
  • A set of CSS styles attached to a CSS selector that determine various details of the animation, such as its timing and duration. Whenever an element matches the specified selector, the associated animation will run. The following code attaches the above stretch animation to any element with the .widget class that is being hovered over. The animation runs for 2 seconds, after 0.3 second delay.

    .widget:hover
      animation-name: stretch
      animation-duration: 2s
      animation-delay: 0.3s
    

The resulting code works like this:

With these building blocks, CSS animations provide us fine-grain control over every step of the rendering process. Combined with the CSS transform property, it is possible to create incredibly intricate animations – check out Animate.css for some amazing animations written purely in CSS.

Reacting to Property Changes

There are downsides, however – CSS animations are built to run immediately after the specified element is rendered, and consequently can be difficult to use when animating a rendered element as it’s CSS properties change. For example, you cannot declare that every time an element’s height changes, a particular animation should fire.

CSS transitions, on the other hand, are much less powerful, but are often better for managing elements that change in predictable ways. To use a CSS transition, you simply declare how a property of an element should transition when changed. For instance:

.widget
  width: 100px
  transition: width 2s linear

.widget:hover
  width: 200px

The above element would take 2s to transition whenever its width value is changed, which in this case would occur when the element is hovered over. The “linear” keyword determines the timing function of the transition. You can learn more about timing functions and how they work on the Mozilla developer docs.

The resulting code would look like this:

In the above example, attaching a transition to the .widget class actually gave us two animations – it automatically animated the element’s change from 100px to 200px, as well as the change from 200px to 100px. If we wanted to do the same with CSS animations, we would have to create two keyframe sets that are functionally inverse of each other, and attach both as unique animations.

When thinking about our original problem, it would relatively easy to model with CSS transitions. We could simply tell the specified HTML element to transition its background-color property:

It works like a charm! Unfortunately, our specification requires us to use a complex animation that cannot be easily constructed using a CSS transition. If we need to create a custom keyframe set, we cannot use the declarative, state-modeling syntax of CSS transitions.

Repeatable Animations

So why did our original code not work? Unlike CSS transitions, it’s difficult to trigger CSS animations based on state changes. Left to its own devices, a CSS animation is not repeatable – it will run once and only once. If an animation is already attached to a given element, the browser will prevent the same element-animation pair from running again. In our case, all four selectors had the same value for the animation-name attribute, so the browser treated them as identical animations.

So then how do we re-trigger an animation that is already attached to the specified element? CSS Tricks has a good rundown of possible strategies. Most answers use JavaScript to trick the browser into thinking an element has changed, using either cloning or setTimeout. There are architectural and performance downsides to those approaches, however, and in general we at NoRedInk try to avoid using JavaScript for animating the DOM.

A CSS-only approach is to simply generate multiple keyframe sets, and attach a different set to each class. While the animations are identical, the browser treats them as distinct, which allows for the animations to be triggered infinitely. While effective, this approach would lead to lots of CSS duplication, as it is impossible in pure CSS to generate multiple keyframe sets in a single declaration.

That’s where Sass comes in. Here’s an implementation of the above strategy using a custom built mixin:

As you can see, we’ve built a unique-pulse helper that creates a unique, but functionally identical, keyframe set and attaches it to each class. It takes advantage of the Sass unique-id function, which we can use to generate a unique animation-name value every time the mixin is called.

Combined with the fact that keyframe set declarations bubble up the tree, regardless of where they are declared, we can generate as many “unique” keyframe sets as necessary. The eventual code looks something like this:

@mixin unique-pulse
  $animation-name: unique-id()
  # pulse animation keyframes
  @keyframes #{$animation-name}
    0%
      transform: scale(1)
    50%
      transform: scale(1.1)
    100%
      transform: scale(1)
  animation-duration: 1s
  animation-timing-function: ease
  animation-name: $animation-name

There are some limitations to this approach – all possible states of the element must have their own class, and the element will not animate unless there is a class change. For more control over when the animation is fired, we would have to use JavaScript. That said, this is a relatively simple technique for adding repeatable animations to your application without adding much complexity to your front-end architecture.

If you’re interested in architecting maintainble front-end codebases, NoRedInk would love to work with you! Check out our jobs page for more information.

Discuss this post on Hacker News


Srinivas Rao
@raorao_
Engineer at NoRedInk

3 notes

  1. jalbertbowdenii reblogged this from noredinktech
  2. noredinktech posted this