Using Custom Properties to Wrangle Variations in Keyframe Animations

Avatar of Sandrina Pereira
Sandrina Pereira on

Have you ever wondered how to customize CSS animations keyframes without using any preprocessor feature, like mixins? I keep reaching for preprocessors for this reason, but it would be so nice to drop yet one more dependency and go with vanilla CSS.

Well, I found a way to account for variations within a keyframe animation using nothing but CSS and it’s thanks to custom properties! Let’s learn a little more about how CSS keyframes work and how we can enhance them with CSS and a touch of custom properties.

Understanding CSS animations inheritance

When we assign an animation to an element, we can customize some of its properties such as duration, delay, and so on. Let me show you a dummy example: we have two classes: .walk and .run. Both share the same animation (named breath) and .run executes the animation faster than .walk (0.5s to 2s, respectively).

@keyframes breath {
  from {
    transform: scale(0.5);
  }
  to {
    transform: scale(1.5);
  }
}

/* Both share the same animation, but walking is _slower_ than running */
.walk {
  animation: breath 2s alternate;
}

.run {
  animation: breath 0.5s alternate;
}

Each time we reuse an animation, it can behave differently based on the properties we assign it. So, we can say that an animation inherits its behavior based on the element where it’s applied.

But what about the animation *rules* (the scale in this case)? Let’s go back to the breath animation example: The .walk class executes breath slower but it also needs to be deeper, so we need to change its scale value to be larger than it is for the .run class, which takes smaller, more frequent breaths.

The common approach in plain CSS is to duplicate the original animation and tweak the values directly in the class:

@keyframes breath {
  /* same as before... */
}

/* similar to breath, but with different scales */
@keyframes breathDeep {
  from {
    transform: scale(0.3);
  }
  to {
    transform: scale(1.7);
  }
}

.walk {
  animation: breathDeep 2s alternate;
}

.run {
  animation: breath 0.5s alternate;
}

While this works fine, there’s a better solution which allow us to reuse both the animation’s properties and its values! How? By taking advantage of CSS variables inheritance! Let’s see how:

/* breath behaves based on the
CSS variables values that are inherited */
@keyframes breath {
  from {
    transform: scale(var(--scaleStart));
  }
  to {
    transform: scale(var(--scaleEnd));
  }
}

.walk {
  --scaleStart: 0.3;
  --scaleEnd: 1.7;
  animation: breath 2s alternate;
}

.run {
  --scaleStart: 0.8;
  --scaleEnd: 1.2;
  animation: breath 0.5s alternate;
}

Cool, right? Now we don’t need to write duplicate animations to get varying effects from the same animation!

If you need to go even further, remember that we can also update CSS custom properties with JavaScript. Not only root variables, but also in specific elements. I find this incredible powerful because we can create more efficient animations by taking advantage of JavaScript without losing the native optimizations from CSS animation. It’s a win-win!

See the Pen
Dynamic CSS @keyframes w/ CSS Vanilla
by Sandrina Pereira (@sandrina-p)
on CodePen.