130

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/

TylerH
  • 20,799
  • 66
  • 75
  • 101
Khaled
  • 8,255
  • 11
  • 35
  • 56

12 Answers12

77

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 ?

tomByrer
  • 1,105
  • 12
  • 21
Xaltar
  • 1,688
  • 14
  • 22
  • 1
    IE 10 does support keyframe animations. – dlev May 13 '13 at 07:32
  • Thanks for the help, I'm using this code just for illustration, I have edited my question and provided a full code to have a look, unfortunately, the from-to didn't work. – Khaled May 13 '13 at 07:40
  • 4
    That does revert the transition, but the problem is that if the mouse is out before the end of the hover transition we get this "jump". After some searching apparently the state of element must be "persisted" so that we can revert from end rotation position, for that "animation-fill-mode: forwards;" can be used, but it doesn't seem to work. – Khaled May 13 '13 at 08:35
  • It would be easier without `keyframes`. Do you absolutely need `keyframes` ? – Xaltar May 13 '13 at 09:06
  • Always best to check http://caniuse.com/#search=keyframe for current vendor prefixes. – tomByrer May 11 '14 at 18:57
  • 31
    @Xaltar When you first load the app, the first animation plays without hovering over the square. Is there anyway to stop that initial load animation from occurring? – jlewkovich Jan 13 '17 at 20:37
  • The code in the fiddle is not the same code in the answer here. The code in the fiddle works great. – Jeff Diederiks Sep 23 '17 at 01:15
  • 4
    Did anyone find a solution to the first animation playing on its own after refreshing? Kind of important. – user2619824 Jun 29 '19 at 23:26
  • 6
    This answer is ultimately useless if you cannot suppress the animation playing the first time – NearHuscarl Nov 19 '19 at 17:46
  • If you don't want it to start on page load: add this jQuery script! $(".intern").mouseout(function() { $(this).addClass("wasHovered"); }); Then add your out styling on .wasHovered, so it will only play when the link or div has been hovered on at least once. – mateostabio Mar 06 '21 at 15:04
  • Here's an example on how to add jQuery to not have the animation play on page load : https://codepen.io/MateoStabio/pen/jOVvwrM – mateostabio Mar 06 '21 at 15:05
  • Note that you cannot just use the same animation with the 'reverse' flag to animate out. You need a second @keyframes defined going the other direction. – icanfathom Jul 05 '21 at 23:25
47

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

Jordan Lafland
  • 619
  • 5
  • 2
28

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');
    }
);
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Giles Copp
  • 389
  • 3
  • 3
18

Creating a reversed animation is kind of overkill to a simple problem. What you need is:

animation-direction: reverse

However, this won't work on its own because animation spec forgot to add a way to restart the animation, so here is how you do it with the help of JS

let item = document.querySelector('.item')

// play normal
item.addEventListener('mouseover', () => {
  item.classList.add('active')
})

// play in reverse
item.addEventListener('mouseout', () => {
  item.style.opacity = 0 // avoid showing the init style while switching the 'active' class

  item.classList.add('in-active')
  item.classList.remove('active')

  // force dom update
  setTimeout(() => {
    item.classList.add('active')
    item.style.opacity = ''
  }, 5)

  item.addEventListener('animationend', onanimationend)
})

function onanimationend() {
  item.classList.remove('active', 'in-active')
  item.removeEventListener('animationend', onanimationend)
}
@keyframes spin {
  0% {
    transform: rotateY(0deg);
  }
  100% {
    transform: rotateY(180deg);
  }
}

div {
  background: black;
  padding: 1rem;
  display: inline-block;
}

.item {
  /* because span cant be animated */
  display: block;
  color: yellow;
  font-size: 2rem;
}

.item.active {
  animation: spin 1s forwards;
  animation-timing-function: ease-in-out;
}

.item.in-active {
  animation-direction: reverse;
}
<div>
  <span class="item">ABC</span>
</div>
allenski
  • 1,652
  • 4
  • 23
  • 39
ctf0
  • 6,991
  • 5
  • 37
  • 46
6

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​.

function fn(el, isEnter) {
  el.className = "";
   requestAnimationFrame(() => {
    requestAnimationFrame(() => {
        el.className = isEnter? "in": "out";
    });
  });  
}
.in{
  animation: k 1s forwards;
}

.out{
  animation: k 1s forwards;
  animation-direction: reverse;
}

@keyframes k
{
from {transform: rotate(0deg);}
to   {transform: rotate(360deg);}
}
<div style="width:100px; height:100px; background-color:red" 
  onmouseenter="fn(this, true)"
   onmouseleave="fn(this, false)"  
     ></div>
Shishir Arora
  • 5,521
  • 4
  • 30
  • 35
4

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

animation-direction: reverse
Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
Andrew Lazarus
  • 989
  • 2
  • 16
  • 25
3

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);
}
devdrc
  • 1,853
  • 16
  • 21
2

I've put together a CodePen with a CSS-only fix and one with 2 lines of jQuery to fix the on-page load issue. Continue reading to understand the 2 solutions in a simpler version.

https://codepen.io/MateoStabio/pen/jOVvwrM

If you are searching how to do this with CSS only, Xaltar's answer is simple, straightforward, and is the correct solution. The only downside is that the animation for the mouse out will play when the page loads. This happens because to make this work, you style your element with the OUT animation and the :hover with the IN animation.

svg path{
    animation: animateLogoOut 1s;
}
svg:hover path{
    animation: animateLogoIn 1s;
}

@keyframes animateLogoIn {
    from {stroke-dashoffset: -510px;}
    to {stroke-dashoffset: 0px;}
}
@keyframes animateLogoOut {
    from {stroke-dashoffset: 0px;}
    to {stroke-dashoffset: -510px;}
}

Some people found this solution to be useless as it played on page load. For me, this was the perfect solution. But I made a Codepen with both solutions as I will probably need them in the near future.

If you do not want the CSS animation on page load, you will need to use a tiny little script of JS that styles the element with the OUT animation only after the element has been hovered for the first time. We will do this by adding a class of .wasHovered to the element and style the added class with the OUT Animation.

jQuery:

$("svg").mouseout(function() {
    $(this).addClass("wasHovered");
 });

CSS:

svg path{

}

svg.wasHovered path{
    animation: animateLogoOut 1s;
}

svg:hover path{
    animation: animateLogoIn 1s;
}

@keyframes animateLogoIn {
    from {stroke-dashoffset: -510px;}
    to {stroke-dashoffset: 0px;}
}
@keyframes animateLogoOut {
    from {stroke-dashoffset: 0px;}
    to {stroke-dashoffset: -510px;}
}

And voila! You can find all of this and more on my codepen showing in detail the 2 options with an SVG logo hover animation.

https://codepen.io/MateoStabio/pen/jOVvwrM

mateostabio
  • 958
  • 8
  • 11
1

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.

Razvan_TK9692
  • 320
  • 1
  • 10
1

[OP is asking about animation specifically here, but if all you need is to initiate and reverse rotate based on hover state without a specific animation-iteration-count (though you could possibly calculate the degrees to mimic it), you can use the below.]

As of May 2023, we can accomplish this with just an individual transform (relevant CanIUse) and a transition.

.box {
  rotate: 0deg;
  transition: rotate 2s;
}

.box:hover {
  rotate: 360deg;
}

Some things to note:

  • Mobile implementation for individual transforms is very new (as recent as two weeks before this post), so for full support, either fall back to the original syntax or use LightningCSS/Autoprefixer

View an example below or on Codepen:

.box {
  transition: rotate 2s;
  rotate: 0deg;
}
.box:hover {
  rotate: 360deg;
}

.box:nth-of-type(5):hover {
  rotate: 1080deg;
}


/* visuals (ignore) */
.box {
  --d: 90px;
  height: var(--d);
  width: var(--d);
  border: 2px solid;
  display: flex;
  align-items: center;
  justify-content: center;
}

.container {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
}
<div class="container">
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
  <div class="box">4</div>
  <div class="box">5</div>
</div>
mnicole
  • 83
  • 1
  • 6
0

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+

Ansipants
  • 35
  • 7
0
.class{transition: 1s}
.class:hover{transform: rotate(360deg)
                        translate(100px,100px)
                        rotate(0deg)
                        scale(1.1,1.1)
                        skewX(10deg)
                        skewY(10deg) }

transition: 1s; --- this is a well-known method, it's called "reverse" - it's just a word, not a property.

it returns ALL properties to their original state. In older versions CSS, it was required to specify "all", now it is not necessary.

serega da
  • 21
  • 4
  • 1
    the answer has already been given above --https://stackoverflow.com/a/66189565/4624370 . -- all -- not necessarily – serega da Jul 20 '23 at 17:32