[css] How to reverse an animation on mouse out after hover

So, it is possible to have reverse animation on mouse out such as:

.class{
   transform: rotate(0deg);

}
.class:hover{
   transform: rotate(360deg);
}

but, when using @keyframes animation, I couldn't get it to work, e.g:

.class{
   animation-name: out;
   animation-duration:2s;

}
.class:hover{
   animation-name: in;
   animation-duration:5s;
   animation-iteration-count:infinite;

}
@keyframe in{
    to {transform: rotate(360deg);}
}

@keyframe out{
    to {transform: rotate(0deg);}
}

What is the optimal solution, knowing that I'd need iterations and animation itself?

http://jsfiddle.net/khalednabil/eWzBm/

This question is related to css animation

The answer is


we can use requestAnimationFrame to reset animation and reverse it when browser paints in next frame.

Also use onmouseenter and onmouseout event handlers to reverse animation direction

As per

Any rAFs queued in your event handlers will be executed in the ?same frame?. Any rAFs queued in a rAF will be executed in the next frame?.

_x000D_
_x000D_
function fn(el, isEnter) {_x000D_
  el.className = "";_x000D_
   requestAnimationFrame(() => {_x000D_
    requestAnimationFrame(() => {_x000D_
        el.className = isEnter? "in": "out";_x000D_
    });_x000D_
  });  _x000D_
}
_x000D_
.in{_x000D_
  animation: k 1s forwards;_x000D_
}_x000D_
_x000D_
.out{_x000D_
  animation: k 1s forwards;_x000D_
  animation-direction: reverse;_x000D_
}_x000D_
_x000D_
@keyframes k_x000D_
{_x000D_
from {transform: rotate(0deg);}_x000D_
to   {transform: rotate(360deg);}_x000D_
}
_x000D_
<div style="width:100px; height:100px; background-color:red" _x000D_
  onmouseenter="fn(this, true)"_x000D_
   onmouseleave="fn(this, false)"  _x000D_
     ></div>
_x000D_
_x000D_
_x000D_


Would you be better off having just the one animation, but having it reverse?

animation-direction: reverse

Try this:

@keyframe in {
from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
@keyframe out {
from {
    transform: rotate(360deg);
  }
  to {
    transform: rotate(0deg);
  }
}

supported in Firefox 5+, IE 10+, Chrome, Safari 4+, Opera 12+


Its much easier than all this: Simply transition the same property on your element

.earth { width:  0.92%;    transition: width 1s;  }
.earth:hover { width: 50%; transition: width 1s;  }

https://codepen.io/lafland/pen/MoEaoG


lol, it has a very easy solution with CSS only. Here you go

#thing { padding: 10px; border-radius: 5px;

/* HOVER OFF */ -webkit-transition: padding 2s; }

#thing:hover { padding: 20px; border-radius: 15px;

/* HOVER ON */ -webkit-transition: border-radius 2s; }


creating a reversed animation is kinda an overkill to a simple problem, what u need is

animation-direction: reverse

however this wont work on its own because animation spec is so dump that they forgot to add a way to restart the animation so here is how you do it with the help of js

_x000D_
_x000D_
let item = document.querySelector('.item')_x000D_
_x000D_
// play normal_x000D_
item.addEventListener('mouseover', () => {_x000D_
  item.classList.add('active')_x000D_
})_x000D_
_x000D_
// play in reverse_x000D_
item.addEventListener('mouseout', () => {_x000D_
  item.style.opacity = 0 // avoid showing the init style while switching the 'active' class_x000D_
_x000D_
  item.classList.add('in-active')_x000D_
  item.classList.remove('active')_x000D_
_x000D_
  // force dom update_x000D_
  setTimeout(() => {_x000D_
    item.classList.add('active')_x000D_
    item.style.opacity = ''_x000D_
  }, 5)_x000D_
_x000D_
  item.addEventListener('animationend', onanimationend)_x000D_
})_x000D_
_x000D_
function onanimationend() {_x000D_
  item.classList.remove('active', 'in-active')_x000D_
  item.removeEventListener('animationend', onanimationend)_x000D_
}
_x000D_
@keyframes spin {_x000D_
  0% {_x000D_
    transform: rotateY(0deg);_x000D_
  }_x000D_
  100% {_x000D_
    transform: rotateY(180deg);_x000D_
  }_x000D_
}_x000D_
_x000D_
div {_x000D_
  background: black;_x000D_
  padding: 1rem;_x000D_
  display: inline-block;_x000D_
}_x000D_
_x000D_
.item {_x000D_
  /* because span cant be animated */_x000D_
  display: block;_x000D_
  color: yellow;_x000D_
  font-size: 2rem;_x000D_
}_x000D_
_x000D_
.item.active {_x000D_
  animation: spin 1s forwards;_x000D_
  animation-timing-function: ease-in-out;_x000D_
}_x000D_
_x000D_
.item.in-active {_x000D_
  animation-direction: reverse;_x000D_
}
_x000D_
<div>_x000D_
  <span class="item">ABC</span>_x000D_
</div>
_x000D_
_x000D_
_x000D_


I don't think this is achievable using only CSS animations. I am assuming that CSS transitions do not fulfil your use case, because (for example) you want to chain two animations together, use multiple stops, iterations, or in some other way exploit the additional power animations grant you.

I've not found any way to trigger a CSS animation specifically on mouse-out without using JavaScript to attach "over" and "out" classes. Although you can use the base CSS declaration trigger an animation when the :hover ends, that same animation will then run on page load. Using "over" and "out" classes you can split the definition into the base (load) declaration and the two animation-trigger declarations.

The CSS for this solution would be:

.class {
    /* base element declaration */
}
.class.out {
   animation-name: out;
   animation-duration:2s;

}
.class.over {
   animation-name: in;
   animation-duration:5s;
   animation-iteration-count:infinite;
}
@keyframes in {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}
@keyframes out {
    from {
        transform: rotate(360deg);
    }
    to {
        transform: rotate(0deg);
    }
}

And using JavaScript (jQuery syntax) to bind the classes to the events:

$(".class").hover(
    function () {
        $(this).removeClass('out').addClass('over');
    },
    function () {
        $(this).removeClass('over').addClass('out');
    }
);

Using transform in combination with transition works flawlessly for me:

.ani-grow {
    -webkit-transition: all 0.5s ease; 
    -moz-transition: all 0.5s ease; 
    -o-transition: all 0.5s ease; 
    -ms-transition: all 0.5s ease; 
    transition: all 0.5s ease; 
}
.ani-grow:hover {
    transform: scale(1.01);
}

Have tried several solutions here, nothing worked flawlessly; then Searched the web a bit more, to find GSAP at https://greensock.com/ (subject to license, but it's pretty permissive); once you reference the lib ...

 <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>

... you can go:

  var el = document.getElementById('divID');    

  // create a timeline for this element in paused state
  var tl = new TimelineMax({paused: true});

  // create your tween of the timeline in a variable
  tl
  .set(el,{willChange:"transform"})
  .to(el, 1, {transform:"rotate(60deg)", ease:Power1.easeInOut});

  // store the tween timeline in the javascript DOM node
  el.animation = tl;

  //create the event handler
  $(el).on("mouseenter",function(){
    //this.style.willChange = 'transform';
    this.animation.play();
  }).on("mouseleave",function(){
     //this.style.willChange = 'auto';
    this.animation.reverse();
  });

And it will work flawlessly.


I think that if you have a to, you must use a from. I would think of something like :

@keyframe in {
    from: transform: rotate(0deg);
    to: transform: rotate(360deg);
}

@keyframe out {
    from: transform: rotate(360deg);
    to: transform: rotate(0deg);
}

Of course must have checked it already, but I found strange that you only use the transform property since CSS3 is not fully implemented everywhere. Maybe it would work better with the following considerations :

  • Chrome uses @-webkit-keyframes, no particuliar version needed
  • Safari uses @-webkit-keyframes since version 5+
  • Firefox uses @keyframes since version 16 (v5-15 used @-moz-keyframes)
  • Opera uses @-webkit-keyframes version 15-22 (only v12 used @-o-keyframes)
  • Internet Explorer uses @keyframes since version 10+

EDIT :

I came up with that fiddle :

http://jsfiddle.net/JjHNG/35/

Using minimal code. Is it approaching what you were expecting ?