Search code examples
htmlcsscss-animations

How to use "reverse" for a hover and out event


Given this code (http://jsfiddle.net/bzf1mkx5/)

.intern {
    -webkit-animation: in 1s 1 reverse forwards;
}

.intern:hover {
    -webkit-animation: in 1s 1 normal forwards;
}

@-webkit-keyframes in {
    from   { -webkit-transform: scale(1); }
    to { -webkit-transform: scale(1.2);}
}
<div style = "width : 100px; height : 100px; background-color : red" class ="intern"></div>

Why are animations absent? Where is the mistake?


By using two separated animations, it does work, but then what's the point of having a reverse property?

.intern {
    -webkit-animation: in2 1s 1 reverse forwards;
}

.intern:hover {
    -webkit-animation: in 1s 1 normal forwards;
}

@-webkit-keyframes in {
    from   { -webkit-transform: scale(1); }
    to { -webkit-transform: scale(1.2);}
}
@-webkit-keyframes in2 {
    from   { -webkit-transform: scale(1); }
    to { -webkit-transform: scale(1.2);}
}
<div style = "width : 100px; height : 100px; background-color : red" class ="intern"></div>


If I use higher times, I should see the animations, even though I will see the unwanted effect of jumping from one state to another.

.intern {
    -webkit-animation: in 20s 1 reverse forwards;
}

.intern:hover {
    -webkit-animation: in 20s 1 normal forwards;
}

@-webkit-keyframes in {
    from   { -webkit-transform: scale(1); }
    to { -webkit-transform: scale(4);}
}
<div style = "width : 50px; height : 50px; background-color : red" class ="intern"></div>


Solution

  • Let's first understand what reverse mean

    The animation plays backwards each cycle. In other words, each time the animation cycles, the animation will reset to the end state and start over again. Animation steps are performed backwards, and timing functions are also reversed. For example, an ease-in timing function becomes ease-out. ref

    Let's take a basic example to understand:

    .box {
      width:50px;
      height:50px;
      margin:5px;
      background:red;
      position:relative;
    }
    .normal {
      animation: move normal  2s linear forwards;
    }
    .reverse {
      animation: move reverse 2s linear forwards;
    }
    
    @keyframes move{
      from {
        left:0;
      }
      to {
        left:300px;
      }
    }
    <div class="box normal">
    
    </div>
    
    <div class="box reverse">
    
    </div>

    We have one animation and the first element is runnig it forwards while the second element is running it backwards. Technically, it's like we invert the from and to. reverse is useful in order to avoid defining a new animation in the opposite direction. We define a left-to-right animation and with reverse we have the right-to-left one. Until now, it's trivial.

    Now, if you run any kind of animation defined by its name and in the road you modify a property of this animation you will change the behavior of the whole animation and we calculate again the value of the current state based on the new animation.

    Example:

    .box {
      width:50px;
      height:30px;
      margin:5px;
      background:red;
      position:relative;
    }
    .normal,
    .special{
      animation: move normal  20s linear forwards;
      background:blue;
    }
    .reverse,
    div:hover .special{
      animation: move reverse 20s linear forwards;
      background:green;
    }
    
    @keyframes move{
      from {
        left:0;
      }
      to {
        left:300px;
      }
    }
    
    p {
     margin:0;
    }
    <p>Reference</p>
    <div class="box normal"></div>
    
    <div class="box reverse"></div>
    <p>Hover the element</p>
    <div>
       <div class="box special"></div> 
    </div>

    We have 3 elements animating using the same animation at the same time so they will all be at the same progress. The third one is the same as the first animation and on hover it become the same as the second one. The trick is here! on hover you will not consider the actual position of the element and simply move in the other direction but you will consider the new animation defined by reverse and calculate the value of left considering the actual progress of the animation.

    If the element is at 5s of the animation in the normal state we will have left:100px and in the reverse state we will have left:200px so if you hover at 5s you will toggle between 100px and 200px. You will not consider left:100px and move back to the 0px

    Now it clear that if the animation is over, you will simply toggle between the first and the last state. You will not trigger a new animation again since we are dealing with the same animation and we simply changed one setting (the direction)

    .box {
      width:50px;
      height:30px;
      margin:5px;
      background:red;
      position:relative;
    }
    .normal,
    .special{
      animation: move normal  1s linear forwards;
      background:blue;
    }
    .reverse,
    div:hover .special{
      animation: move reverse 1s linear forwards;
      background:green;
    }
    
    @keyframes move{
      from {
        left:0;
      }
      to {
        left:300px;
      }
    }
    
    p {
     margin:0;
    }
    <p>Reference</p>
    <div class="box normal"></div>
    
    <div class="box reverse"></div>
    <p>Hover the element</p>
    <div>
       <div class="box special"></div> 
    </div>


    Same logic happen if you change any other property. You will not see an intuitive result because you need to imagine how the new animation will be in order to identify how the current state will become.

    Example if we change the timing-function

    .box {
      width:50px;
      height:30px;
      margin:5px;
      background:red;
      position:relative;
    }
    .normal,
    .special{
      animation: move  20s linear forwards;
      background:blue;
    }
    .reverse,
    div:hover .special{
      animation: move 20s ease forwards;
      background:green;
    }
    
    @keyframes move{
      from {
        left:0;
      }
      to {
        left:300px;
      }
    }
    
    p {
     margin:0;
    }
    <p>Reference</p>
    <div class="box normal"></div>
    
    <div class="box reverse"></div>
    <p>Hover the element</p>
    <div>
       <div class="box special"></div> 
    </div>

    Example if we change the duration:

    .box {
      width:50px;
      height:30px;
      margin:5px;
      background:red;
      position:relative;
    }
    .normal,
    .special{
      animation: move  20s linear forwards;
      background:blue;
    }
    .reverse,
    div:hover .special{
      animation: move 30s linear forwards;
      background:green;
    }
    
    @keyframes move{
      from {
        left:0;
      }
      to {
        left:300px;
      }
    }
    
    p {
     margin:0;
    }
    <p>Reference</p>
    <div class="box normal"></div>
    
    <div class="box reverse"></div>
    <p>Hover the element</p>
    <div>
       <div class="box special"></div> 
    </div>


    What you are looking for can be achieved with transition because transition will only care about the current value and will transition to a new one when you want (on hover for example)

    .intern {
      width: 100px;
      height: 100px;
      margin:50px;
      background-color: red;
      transition:10s all;
      
    }
    
    .intern:hover {
      transform:scale(4);
    }
    <div style="" class="intern"></div>

    When you hover you will start a scale animation that will last 10s BUT if you unhover before you will get back to the initial state. You cannot do this with animation. An animation need to either run fully or you abruptly cancel it in the middle and you get back to initial state

    Even if you define two animation you will not always have the needed result in you hover when the running one is not yet over. You will cancel it and run the new one.

    .intern {
      width: 100px;
      height: 100px;
      margin:50px;
      background-color: red;
      animation:in 5s linear forwards; 
    }
    
    .intern:hover {
      animation:out 5s linear forwards;
    }
    
    @keyframes in {
      from {
        transform:scale(1);
      }
      to {
        transform:scale(4);
      }
    }
    @keyframes out {
      from {
        transform:scale(4);
      }
      to {
        transform:scale(1);
      }
    }
    <div style="" class="intern"></div>

    Try to hover/unhover rapidly and you will see the bad effect. You will only have a perfect result if you hover at the end of each animation.