Possible to reverse a css animation on class removal?

JavascriptJqueryHtmlCss

Javascript Problem Overview


Essentially what I'm trying to do is give an element a CSS animation when it gains a class, then reverse that animation when I remove the class without playing the animation when the DOM renders.

Fiddle here: http://jsfiddle.net/bmh5g/

As you can see in the fiddle, when you hover the "Hover Me" button, #item flips down. When you mouseoff the hover button, #item just disappears. I want #item to flip back up (ideally using the same animation but in reverse). Is this possible?

$('#trigger').on({
  mouseenter: function() {
    $('#item').addClass('flipped');
  },
  mouseleave: function() {
    $('#item').removeClass('flipped');
  }
})

#item {
  position: relative;
  height: 100px;
  width: 100px;
  background: red;
  -webkit-transform: perspective(350px) rotateX(-90deg);
  transform: perspective(350px) rotateX(-90deg);
  -webkit-transform-origin: 50% 0%;
  transform-origin: 50% 0%;
}

#item.flipped {
  animation: flipper 0.7s;
  animation-fill-mode: forwards;
  -webkit-animation: flipper 0.7s;
  -webkit-animation-fill-mode: forwards;
}

@keyframes flipper {
  0% {
    transform: perspective(350px) rotateX(-90deg);
  }
  33% {
    transform: perspective(350px) rotateX(0deg);
  }
  66% {
    transform: perspective(350px) rotateX(10deg);
  }
  100% {
    transform: perspective(350px) rotateX(0deg);
  }
}

@-webkit-keyframes flipper {
  0% {
    -webkit-transform: perspective(350px) rotateX(-90deg);
  }
  33% {
    -webkit-transform: perspective(350px) rotateX(0deg);
  }
  66% {
    -webkit-transform: perspective(350px) rotateX(10deg);
  }
  100% {
    -webkit-transform: perspective(350px) rotateX(0deg);
  }
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='trigger'>Hover Me</div>
<div id='item'></div>

Javascript Solutions


Solution 1 - Javascript

I would have the #item start out hidden with the reverse animation by default. Then add the class to give it the animation and show the #item. http://jsfiddle.net/bmh5g/12/

$('#trigger').on({
  mouseenter: function() {
    $('#item').show();
    $('#item').addClass('flipped');
  },
  mouseleave: function() {
    $('#item').removeClass('flipped');
  }
});

#trigger {
  position: relative;
  display: inline-block;
  padding: 5px 10px;
  margin: 0 0 10px 0;
  background: teal;
  color: white;
  font-family: sans-serif;
}

#item {
  position: relative;
  height: 100px;
  width: 100px;
  background: red;
  display: none;
  -webkit-transform: perspective(350px) rotateX(-90deg);
  transform: perspective(350px) rotateX(-90deg);
  -webkit-transform-origin: 50% 0%;
  transform-origin: 50% 0%;
  animation: flipperUp 0.7s;
  animation-fill-mode: forwards;
  -webkit-animation: flipperUp 0.7s;
  -webkit-animation-fill-mode: forwards;
}

#item.flipped {
  animation: flipper 0.7s;
  animation-fill-mode: forwards;
  -webkit-animation: flipper 0.7s;
  -webkit-animation-fill-mode: forwards;
}

@keyframes flipper {
  0% {
    transform: perspective(350px) rotateX(-90deg);
  }
  33% {
    transform: perspective(350px) rotateX(0deg);
  }
  66% {
    transform: perspective(350px) rotateX(10deg);
  }
  100% {
    transform: perspective(350px) rotateX(0deg);
  }
}

@-webkit-keyframes flipper {
  0% {
    -webkit-transform: perspective(350px) rotateX(-90deg);
  }
  33% {
    -webkit-transform: perspective(350px) rotateX(0deg);
  }
  66% {
    -webkit-transform: perspective(350px) rotateX(10deg);
  }
  100% {
    -webkit-transform: perspective(350px) rotateX(0deg);
  }
}

@keyframes flipperUp {
  0% {
    transform: perspective(350px) rotateX(0deg);
  }
  33% {
    transform: perspective(350px) rotateX(10deg);
  }
  66% {
    transform: perspective(350px) rotateX(0deg);
  }
  100% {
    transform: perspective(350px) rotateX(-90deg);
  }
}

@-webkit-keyframes flipperUp {
  0% {
    -webkit-transform: perspective(350px) rotateX(0deg);
  }
  33% {
    -webkit-transform: perspective(350px) rotateX(10deg);
  }
  66% {
    -webkit-transform: perspective(350px) rotateX(0deg);
  }
  100% {
    -webkit-transform: perspective(350px) rotateX(-90deg);
  }
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div id='trigger'>Hover Me</div>
<div id='item'></div>

Solution 2 - Javascript

Another approach, rather than using display: none, is to suppress the reverse animation with a class on page load, and then remove that class with the same event that applies the normal animation (eg: flipper). Like so (http://jsfiddle.net/astrotim/d7omcbrz/1/):

CSS - in addition to the flipperUp keyframe posted by Blake above

#item.no-animation 
{
  animation: none;
}

jQuery

$('#trigger').on({
    mouseenter: function(){
        $('#item').removeClass('no-animation');
        $('#item').addClass('flipped');
    },
    mouseleave: function(){
        $('#item').removeClass('flipped');
    }
})

Solution 3 - Javascript

In addition to the answers here, please cache your $(selector)

So you pretty much do this var elements = $(selector); to cache.

Why?! Because if you use the code in the answers on this page as is you will ask the DOM for that same element collection ($('#item')) each time. DOM reading is an expensive operation.

For example, the accepted answer would look something like so:

var item = $('#item');
$('#trigger').on({
    mouseenter: function(){
        item.show();
        item.addClass('flipped');
    },
    mouseleave: function(){
        item.removeClass('flipped');
    }
});

Since I've written all this text, might as well answer your question using CSS transitions

I know you asked for a CSS animations example, but for the animation you wanted to do (a card flipping open), it can be easily achieved using CSS transitions:

#item {
  width: 70px;
  height: 70px;
  background-color: black;
  line-height: 1;
  color: white;
}

#item+div {
  width: 70px;
  height: 100px;
  background-color: blue;
  transform: perspective(250px) rotateX(-90deg);
  transform-origin: 50% 0%;
  transition: transform .25s ease-in-out
}

#item:hover+div {
  transform: perspective(250px) rotateX(0);
}

<div id="item"></div>
<div></div>

Solution 4 - Javascript

Its animating down using css so to get it to animate up you need to create a class, say .item-up that does the transformation in the opposite so then you would remove the previous class and add the item-up class and that should animate it up.

I would write you a js fiddle for it but I dont know the syntax well enough.

Basically when you will need:

@keyframes flipper
@keyframes flipper-up  //This does the opposite of flipper

and

$('#trigger').on({
    mouseenter: function(){
        $('#item').removeClass('flipped-up');
        $('#item').addClass('flipped');
    },
    mouseleave: function(){
        $('#item').removeClass('flipped');
        $('#item').addClass('flipped-up');
    }
})

jsfiddle.net/bmh5g/3 courtesy of Jake

Solution 5 - Javascript

CSS solution from MDN and almost supported by all browser

.animation(animationName 10s ease-in-out infinite alternate both running;)

Solution 6 - Javascript

You can make use of the attribute animation-direction to run the same animation in reverse. If you couple this with one of the many methods described here for restarting an animation- we can start the animation forwards on mouseenter, then on mouseleave we can restart it and play it in reverse.

I don't know how to use jQuery very well, so I chose one of the non-jQuery methods mentioned in the article.

const element_button = document.getElementById('trigger');
const element_item = document.getElementById('item');

element_button.addEventListener("mouseenter", () => {
  if (element_item.classList.contains('animate-backwards')) {
    element_item.classList.remove('animate-backwards');
    void element_item.offsetWidth;
  }
  
  element_item.classList.add('animate-forwards');
});

element_button.addEventListener("mouseleave", () => {
  element_item.classList.remove('animate-forwards');
  void element_item.offsetWidth;
  
  element_item.classList.add('animate-backwards');
});

and

#item.animate-forwards {
  animation: flipper 0.7s normal;
  -webkit-animation: flipper 0.7s normal;
  
  animation-fill-mode: forwards;
  -webkit-animation-fill-mode: forwards;
}

#item.animate-backwards {
  animation: flipper 0.7s reverse;
  -webkit-animation: flipper 0.7s reverse;
  
  animation-fill-mode: forwards;
  -webkit-animation-fill-mode: forwards;
}

Here is a jsFiddle for the above code.

Solution 7 - Javascript

Worked fo me: 1 animation in reverse for the Element (from 100% to 0%) 1 separate animation forwards for the new class (from 0% to 100%) And toggling that class would work

  [1]: https://jsfiddle.net/q7bc4s0f/17/

Upd: That way animation will play backwards on page load. To solve this you have to ADD new bacwards animation class on event ONCE and then toggle forwards animation class on that event.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJakeView Question on Stackoverflow
Solution 1 - JavascriptBlake PlumbView Answer on Stackoverflow
Solution 2 - JavascriptAstrotimView Answer on Stackoverflow
Solution 3 - JavascriptKitanga NdayView Answer on Stackoverflow
Solution 4 - JavascriptShaneView Answer on Stackoverflow
Solution 5 - JavascriptB IsaacView Answer on Stackoverflow
Solution 6 - JavascriptKadeView Answer on Stackoverflow
Solution 7 - Javascript3R1PView Answer on Stackoverflow