3

I have a CSS3 animation that I want to use both in normal and reverse order. I want to trigger the animation by toggling two classes. Basically is an animation that makes an element grow, and I want to use it to shrink the element too.

Basically it is:

  • add expanded class: execute animation
  • Remove expanded class, add collapsed class: execute animation in reverse

The first part works correctly, however if I try to use the same animation for both classes the animation stops working, nothing happens.

div {
  max-height: 1em;
  width: 50px;
  background-color: red;
  color: white;
  text-align: center;
  border-radius: 30px;
  padding: 1em 0;
}

.expanded {
  animation: expand 2s;
  background-color: blue;
}

.collapsed {
  background-color: red;
  //animation: expand 2s reverse; // Uncomment and all animations stop working
}

@keyframes expand {
  0% {
    max-height: 1em;
  }
  50% {
    max-height: 1em;
  }
  100% {
    max-height: 200px;
  }
}

Here is a simple jsfiddle that demonstrates this: https://jsfiddle.net/danielo515/jxLcoocs/6/

EDIT:

As @LGSon have suggested using a transition property will be simpler for this case example, but I need to use an animation because I am running several animations and I need to be able to delay some parts of them.

Danielo515
  • 5,996
  • 4
  • 32
  • 66

1 Answers1

10

The approach you use does not work as the element already has the animation expand applied, through the class expanded, so by simply call it again, by adding the class collapsed, where the only difference is the reverse, will not work, as they both reference the same animation.

W3 states: In order to restart an animation, it must be removed then reapplied. (https://www.w3.org/TR/css3-animations/#animations)

So the simplest way to make this work is to use 2 different animations, as shown in below sample.

To that sample I also added the animation fill mode forwards, so it will retain the computed values set by the last keyframe encountered during execution.

Other ways, not shown here, could be:

  • Remove and reapply the animation, note though, for this to look good the element might need to have its end state applied initially, or else it could jump back on removal

  • Use animation-play-state and a script, to pause an animation half way, where the second half does the reverse

  • Use animation-timing-function to add steps to the animation

const example = document.getElementById('example');

let expanded = false;

example.className = 'expanded';

const toogleClass = () => {
  example.className = expanded ? 'expanded' : 'collapsed';
  expanded = !expanded;
}

setInterval(toogleClass, 2500);
div {
  max-height: 1em;
  width: 50px;
  background-color: red;
  color: white;
  text-align: center;
  border-radius: 30px;
  padding: 1em 0;
}

.expanded {
  animation: expand 2s forwards;
  background-color: blue;
}

.collapsed {
  background-color: red;
  animation: collaps 2s;
}

@keyframes expand {
  0% {
    max-height: 1em;
  }
  50% {
    max-height: 1em;
  }
  100% {
    max-height: 200px;
  }
}

@keyframes collaps {
  0% {
    max-height: 200px;
  }
  50% {
    max-height: 200px;
  }
  100% {
    max-height: 1em;
  }
}
<div id="example">
  Hello
  <br>
  <br>
  <br>
  <br> Friends
</div>

Another approach would be to use transition instead, which will give you a much simpler code

const example = document.getElementById('example');

let expanded = false;

setTimeout(function() {example.className = 'expanded';}, 500);

const toogleClass = () => {
  example.className = expanded ? 'expanded' : 'collapsed';
  expanded = !expanded;
}

setInterval(toogleClass, 2500);
div {
  max-height: 1em;
  width: 50px;
  background-color: red;
  color: white;
  text-align: center;
  border-radius: 30px;
  padding: 1em 0;
  transition: max-height 2s;
}

.expanded {
  background-color: blue;
  max-height: 200px;
}
<div id="example">
  Hello
  <br>
  <br>
  <br>
  <br> Friends
</div>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • I've learned you need a second `@keyframe` for toggles to work the "hard way". Since I've stumbled across this one, I was wondering if there is an official explanation? Something along the lines of: *if a particular animation ran on an element, changing it's direction will not make it run again*? – tao Apr 23 '17 at 19:47
  • @AndreiGheorghiu I can't point you to a official resource explaining that, though the guys at CSS-Tricks has an article: https://css-tricks.com/restart-css-animation/ – Asons Apr 23 '17 at 19:59
  • 1
    @AndreiGheorghiu Actually I found this statement at W3: _In order to restart an animation, it must be removed then reapplied._ ... https://www.w3.org/TR/css3-animations/#animations – Asons Apr 23 '17 at 20:02
  • 1
    Not exactly sure why this doesn't works. Theoretically, removing a class and applying another should trigger this change of `animation-name` property. In reality, it doesn't happen, not even if I put the re-application inside a `setTimeout(func, 0);` +1 for researching it. Thank you. – tao Apr 23 '17 at 20:07
  • As @AndreiGheorghiu said, removing a class and adding it should be considered as removing the animation and then re-applying it. Of course I also ended using two keyframes, but I'm still unable to understand why my approach does not work. I need to use an animation because I want to delay it's execution, which is not possible with transitions. Thanks LGSon – Danielo515 Apr 24 '17 at 06:54
  • @Danielo515 Updated my answer with an explanation that hopefully makes more sense, together with a few more ways – Asons Apr 24 '17 at 07:21