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

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

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

Create account Log in
Cover image for Fill Modes
Top
Top

Posted on

Fill Modes

Probably the most confusing aspect of keyframe animations is fill modes. They're the biggest obstacle on our path towards keyframe confidence.

Let's start with a problem.

We want our element to fade out. The animation itself works fine, but when it's over, the element pops back into existence:

<style>
  @keyframes fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }

  .box {
    animation: fade-out 1000ms;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

If we were to graph the element's opacity over time, it would look something like this:

Image description

Why does the element jump back to full visibility? Well, the declarations in the from and to blocks only apply while the animation is running.

After 1000ms has elapsed, the animation packs itself up and hits the road. The declarations in the to block dissipate, leaving our element with whatever CSS declarations have been defined elsewhere. Since we haven't set opacity for this element anywhere else, it snaps back to its default value (1).

One way to solve this is to add an opacity declaration to the .box selector:

<style>
  @keyframes fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }

  .box {
    animation: fade-out 1000ms;
    /*
      Change the "default" value for opacity,
      so that it reverts to 0 when the
      animation completes.
    */
    opacity: 0;
  }
</style>

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

While the animation is running, the declarations in the @keyframes statement overrule the opacity declaration in the .box selector. Once the animation wraps up, though, that declaration kicks in and keeps the box hidden.

Specificity?
In CSS, conflicts are resolved based on the β€œspecificity” of a selector. An ID selector (#login-form) will win the battle against a class one (.thing).
But what about keyframe animations? What is their specificity?
It turns out that specificity isn't really the right way to think about this; instead, we need to think about cascade origins.

A β€œcascade origin” is a source of selectors. For example, browsers come with a bunch of built-in stylesβ€”that's why anchor tags are blue and underlined by default. These styles are part of the User-Agent Origin.

The specificity rules only apply when comparing selectors in the same origin. The styles we write normally are part of the β€œAuthor Origin”, and Author Origin styles win out over ones written in the User-Agent Origin.

Filling forwards

Instead of relying on fallback declarations, let's consider another approach, using animation-fill-mode:

<style>
  @keyframes fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }

  .box {
    animation: fade-out 1000ms;
    animation-fill-mode: forwards;
  }
</style>

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

animation-fill-mode lets us persist the final value from the animation, forwards in time.

Image description
"forwards" is a very confusing name, but hopefully seeing it on this graph makes it a bit clearer!

When the animation ends, animation-fill-mode: forwards will copy/paste the declarations in the final block, persisting them forwards in time.

Filling backwards

We don't always want our animations to start immediately! As with transition, we can specify a delay, with the animation-delay property.

Unfortunately, we run into a similar issue:

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

  .box {
    animation: slide-in 1000ms;
    animation-delay: 500ms;
  }
</style>

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

For that first half-second, the element is fully visible!

Image description

The CSS in the from and to blocks is only applied while the animation is running. Frustratingly, the animation-delay period doesn't count. So for that first half-second, it's as if the CSS in the from block doesn't exist.

animation-fill-mode has another value that can help us here: backwards. This will apply the CSS from the first block backwards in time.

Image description

β€œForwards” and β€œbackwards” are confusing values, but here's an analogy that might help: imagine if we had recorded the user's session from the moment the page loaded. We could scrub forwards and backwards in the video. We can scrub backwards, before the animation has started, or forwards, after the animation has ended.

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

  .box {
    animation: slide-in 1000ms;
    animation-delay: 500ms;
    animation-fill-mode: backwards;
  }
</style>

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

What if we want to persist the animation forwards and backwards? We can use a third value, both, which persists in both directions:

Image description

Personally, I wish that both was the default value. It's so much more intuitive! Though it can make it a bit harder to understand where a particular CSS value has been set.

Like all of the animation properties we're discussing, it can be tossed into the animation shorthand salad:

.box {
  animation: slide-in 1000ms ease-out both;
  animation-delay: 500ms;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
thomasbnt profile image
Thomas Bnt

Very cool explanation with schemas !
I rated your post to hight quality, keep going!

Collapse
 
topcode007 profile image
Top Author

Thank you, Thomas !

🌚 Friends don't let friends browse without dark mode.

Just kidding, it's a personal preference. But you can change your theme, font, etc. in your settings.

The more you know. 🌈