How to force an animation to finish and then reverse to start on hover, even when you stop hovering mid way through?

Viewed 65

I found this codepen which I found really interesting, and am trying to make start on hover, and then stay open until you stop hovering, and it would then reverse and close. My issue is that first of all, it either does the animation on hover and then off hovers jumps straight to the start. Also when leaving hover midway through the animation, it simply jumps back to the start. Ive tried ".box:hover and box:hover:after" with no success. Any help would be appreciated!

The Pen
Disclaimer, this is not my pen, I found it and thought it was cool. Credit to the creator

 *{
      margin:0;
      padding:0;
    }
    .container {
      background: #f0f0f0;
      box-sizing: border-box;
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100vw;
      height: 100vh;
    }
    .box {
      border-radius: 20px;
      width: 60px;
      height: 60px;
      background: #f0f0f0;
      box-shadow:  0 0 0 #cccccc,
                   0 0 0 #ffffff,
                    10px 10px 10px #cccccc inset,
                    -10px -10px 10px #ffffff inset;
      animation: anime 3s cubic-bezier(0.16, 1, 0.3, 1) 1s infinite alternate;
      /* animation-fill-mode: forwards; */
    }

    @keyframes anime {
      0% {
        width: 60px;
        height: 60px;
        background: #f0f0f0;
        box-shadow:  0 0 0 #cccccc,
                     0 0 0 #ffffff,
                      10px 10px 10px #cccccc inset,
                      -10px -10px 10px #ffffff inset;
      }
      25% {
        width: 60px;
        height: 60px;
        background: #f8f8f8;
        box-shadow:  10px 10px 10px #cccccc,
                     10px 10px 10px #ffffff,
                     0 0 0 #cccccc inset,
                     0 0 0 #ffffff inset;
      }
      50% {
        width: 60px;
        height: 240px;
        background: #f8f8f8;
        box-shadow:  10px 10px 10px #cccccc,
                     10px 10px 10px #ffffff,
                     0 0 0 #cccccc inset,
                     0 0 0 #ffffff inset;
      }
      100% {
        width: 480px;
        height: 240px;
        background: #fafafa;
        box-shadow:  40px 40px 40px #cccccc,
                     0 0 0 #ffffff,
                     0 0 0 #cccccc inset,
                     2px 2px 2px #ffffff inset;
      }
<body>
    <div class="container">
      <div class="box"></div>
    </div>
</body>

2 Answers

Read about animation-play-state and look at the hidden snippet.
If you combine those two, you get what you need:

*{
margin:0;
padding:0;
}
.container {
background: #f0f0f0;
box-sizing: border-box;
display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
}
.box {
border-radius: 20px;
  width: 60px;
  height: 60px;
  background: #f0f0f0;
  box-shadow:  0 0 0 #cccccc,
             0 0 0 #ffffff,
              10px 10px 10px #cccccc inset,
              -10px -10px 10px #ffffff inset;
  animation: anime 3s cubic-bezier(0.16, 1, 0.3, 1) 1s infinite alternate;
  animation-play-state: paused; /* added */
  /* animation-fill-mode: forwards; */
}
body:hover .container .box {
  animation-play-state: paused; /* added */
}
body .container .box:hover {
  animation-play-state: running; /* added */
}
@keyframes anime {
  0% {
    width: 60px;
    height: 60px;
    background: #f0f0f0;
    box-shadow:  0 0 0 #cccccc,
                 0 0 0 #ffffff,
                  10px 10px 10px #cccccc inset,
                  -10px -10px 10px #ffffff inset;
  }
  25% {
    width: 60px;
    height: 60px;
    background: #f8f8f8;
    box-shadow:  10px 10px 10px #cccccc,
                 10px 10px 10px #ffffff,
                 0 0 0 #cccccc inset,
                 0 0 0 #ffffff inset;
  }
  50% {
    width: 60px;
    height: 240px;
    background: #f8f8f8;
    box-shadow:  10px 10px 10px #cccccc,
                 10px 10px 10px #ffffff,
                 0 0 0 #cccccc inset,
                 0 0 0 #ffffff inset;
  }
  100% {
    width: 480px;
    height: 240px;
    background: #fafafa;
    box-shadow:  40px 40px 40px #cccccc,
                 0 0 0 #ffffff,
                 0 0 0 #cccccc inset,
                 2px 2px 2px #ffffff inset;
  }
}
<body>
  <div class="container">
    <div class="box"></div>
  </div>
</body>

Initially, the animation is paused. When the body is hovered but the box isn't, the animation is paused. When the box is hovered, the animation runs. I think it's what you needed.

I guess the the answer to your question would be something like this

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)"  
    />
Related