DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,547 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Functions For Animation
Top
Top

Posted on

Functions For Animation

Timing functions

We have access to the same library of timing functions for our keyframe animations. And, like with transition, the default value is ease.

We can override it with the animation-timing-function property:

<style>
  @keyframes slide-in {
    from {
      transform: translateX(-100%);
    }
    to {
      transform: translateX(0%);
    }
  }

  .box {
    animation: slide-in 1000ms;
    animation-timing-function: linear;
  }
</style>

<div class="box">
  Hello World
</div>
Enter fullscreen mode Exit fullscreen mode

Looped animations

By default, keyframe animations will only run once, but we can control this with the animation-iteration-count property:

<style>
  @keyframes slide-in {
    from {
      transform: translateX(-100%);
      opacity: 0.25;
    }
    to {
      transform: translateX(0%);
      opacity: 1;
    }
  }

  .box {
    animation: slide-in 1000ms;
    animation-iteration-count: 3;
  }
</style>

<div class="box">
  Hello World
</div>
Enter fullscreen mode Exit fullscreen mode

It's somewhat rare to specify an integer like this, but there is one special value that comes in handy: infinite.

For example, we can use it to create a loading spinner:

<style>
  @keyframes spin {
    from {
      transform: rotate(0turn);
    }
    to {
      transform: rotate(1turn);
    }
  }

  .spinner {
    animation: spin 1000ms;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
  }
</style>

<img
  class="spinner"
  alt="Loading…"
  src="/images/keyframe-animations/loader.svg"
/>
Enter fullscreen mode Exit fullscreen mode

Note that for spinners, we generally want to use a linear timing function so that the motion is constant (though this is somewhat subjectiveβ€”try changing it and see what you think!).

Multi-step animations

In addition to the from and to keywords, we can also use percentages. This allows us to add more than 2 steps:

<style>
  @keyframes fancy-spin {
    0% {
      transform: rotate(0turn) scale(1);
    }
    25% {
      transform: rotate(1turn) scale(1);
    }
    50% {
      transform: rotate(1turn) scale(1.5);
    }
    75% {
      transform: rotate(0turn) scale(1.5);
    }
    100% {
      transform: rotate(0turn) scale(1);
    }
  }

  .spinner {
    animation: fancy-spin 2000ms;
    animation-iteration-count: infinite;
  }
</style>

<img
  class="spinner"
  alt="Loading…"
  src="/images/keyframe-animations/loader.svg"
/>
Enter fullscreen mode Exit fullscreen mode

The percentages refer to the progress through the animation. from is really just syntactic sugar? for 0%. And to is sugar for 100%.

Importantly, the timing function applies to each step. We don't get a single ease for the entire animation.

In this playground, both spinners complete 1 full rotation in 2 seconds. But multi-step-spin breaks it into 4 distinct steps, and each step has the timing function applied:

<style>
  @keyframes spin {
    0% {
      transform: rotate(0turn);
    }
    100% {
      transform: rotate(1turn)
    }
  }

  @keyframes multi-step-spin {
    0% {
      transform: rotate(0turn);
    }
    25% {
      transform: rotate(0.25turn);
    }
    50% {
      transform: rotate(0.5turn);
    }
    75% {
      transform: rotate(0.75turn);
    }
    100% {
      transform: rotate(1turn);
    }
  }

  .spinner {
    animation: spin 2000ms;
    animation-iteration-count: infinite;
  }
  .multi-step-spinner {
    animation: multi-step-spin 2000ms;
    animation-iteration-count: infinite;
  }
</style>

<img
  class="spinner"
  alt="Loading…"
  src="/images/keyframe-animations/loader.svg"
/>
<img
  class="multi-step-spinner"
  alt="Loading…"
  src="/images/keyframe-animations/loader.svg"
/>
Enter fullscreen mode Exit fullscreen mode

Unfortunately, we can't control this behaviour using CSS keyframe animations, though it is configurable using the Web Animations API.

Alternating animations

Let's suppose that we want an element to "breathe", inflating and deflating.

We could set it up as a 3-step animation:

<style>
  @keyframes grow-and-shrink {
    0% {
      transform: scale(1);
    }
    50% {
      transform: scale(1.5);
    }
    100% {
      transform: scale(1);
    }
  }

  .box {
    animation: grow-and-shrink 4000ms;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }
</style>

<div class="box"></div>
Enter fullscreen mode Exit fullscreen mode

It spends the first half of the duration growing to be 1.5x its default size. Once it reaches that peak, it spends the second half shrinking back down to 1x.

This works, but there's a more-elegant way to accomplish the same effect. We can use the animation-direction property:

<style>
  @keyframes grow-and-shrink {
    0% {
      transform: scale(1);
    }
    100% {
      transform: scale(1.5);
    }
  }

  .box {
    animation: grow-and-shrink 2000ms;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    animation-direction: alternate;
  }
</style>

<div class="box"></div>
Enter fullscreen mode Exit fullscreen mode

animation-direction controls the order of the sequence. The default value is normal, going from 0% to 100% over the course of the specified duration.

We can also set it to reverse. This will play the animation backwards, going from 100% to 0%.

The interesting part, though, is that we can set it to alternate, which ping-pongs between normal and reverse on subsequent iterations.

Instead of having 1 big animation that grows and shrinks, we set our animation to grow, and then reverse it on the next iteration, causing it to shrink.

Half the duration
Originally, our "breathe" animation lasted 4 seconds. When we switched to the alternate strategy, however, we cut the duration in half, down to 2 seconds.
This is because each iteration only performs half the work. It always took 2 seconds to grow, and 2 seconds to shrink. Before, we had a single 4-second-long animation. Now, we have a 2-second-long animation that requires 2 iterations to complete a full cycle.

Shorthand values

We've picked up a lot of animation properties in this lesson, and it's been a lot of typing!

Fortunately, as with transition, we can use the animationshorthand to combine all of these properties.

The above animation can be rewritten:

.box {
  /*
  From this:
    animation: grow-and-shrink 2000ms;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    animation-direction: alternate;
  ...to this:
  */
  animation: grow-and-shrink 2000ms ease-in-out infinite alternate;
}
Enter fullscreen mode Exit fullscreen mode

Here's a piece of good news, as well: the order doesn't matter. For the most part, you can toss these properties in any order you want. You don't need to memorize a specific sequence.

There is an exception: animation-delay, a property we'll talk more about shortly, needs to come after the duration, since both properties take the same value type (milliseconds/seconds).

For this reason, I prefer to exclude delay from the shorthand:

.box {
  animation: grow-and-shrink 2000ms ease-in-out infinite alternate;
  animation-delay: 500ms;
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)

🌚 Life is too short to browse without dark mode