8

With css @keyframes I'm trying to attach some animation to an element when it is clicked.

.animate {
    animation-name: action;
    animation-duration: 2s;
    animation-timing-function: linear;
    background-color: green;
} 

@keyframes action {
    0% {
        background-color: gray; 
    }  

   50% {
       background-color: red;
   }

   100% {
       background-color: green;
   }
}

DEMO

The .animate class is added on click, and the box animates from gray to green. But now I would like to animate back to gray when I click it again (toggle functionality). I tried using the same animation using animation-direction: reverse but no animation was played. The animation should not play initially (I encountered that a couple of times). Any suggestion how I can achieve that?

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Jeanluca Scaljeri
  • 26,343
  • 56
  • 205
  • 333
  • 1
    I think you should make another animation to reverse it. For example you have two classes .animte-gray and .animate-green. And use javascript to toggle the class.Hope it works – dnp1204 Mar 30 '18 at 14:00
  • yep, it seems to be an easy solution: [demo](https://jsfiddle.net/jeanluca/yy3kLyd0/26/) – Jeanluca Scaljeri Mar 30 '18 at 19:17

3 Answers3

8

I would consider using the animation-play-state property and also use infinite and alternate. The idea is to run the animation and stop it when it ends, then run it again and so on:

const div = document.querySelector('.target')
div.addEventListener('click', (e) => {
   div.classList.add('play');
   setTimeout(function() {
      div.classList.remove('play');
   },2000)
})
.target {
  width: 100px;
  height: 100px;
  background-color: gray;
  cursor: pointer;
  animation: action 2s linear alternate infinite;
  animation-play-state:paused;
}
.play {
  animation-play-state:running;
}

@keyframes action {
  0% {
    background-color: gray; 
  }  

  50% {
    background-color: red;
  }

  100% {
     background-color: green;
  }
}
<div class="target">

</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • 1
    @JeanlucaScaljeri considering only one animation it will be difficult in this case when you reduce the time ... maybe a solution is to duplicate the animation – Temani Afif Mar 30 '18 at 23:05
3

Handling this kind of on/off changes with animations is always tricky, and it is very difficult to handle the repeated changes smoothly and seamlessly.

For your case, I would suggest to change it to a single transition. Browser handle much better the on/off part in this case. To achieve a double change in the background color with a transition, create a gradient with 3 colors, and change the gradient position:

const div = document.querySelector('.test')
div.addEventListener('click', (e) => {
   div.classList.toggle('play');
})
.test {
  border: solid 1px black;
  margin: 10px;
  height: 100px;
  width: 100px;
  background-image: linear-gradient(gray 0%, gray 20%, blue 40%, blue 60%,
   red 80%, red 100%);
  background-size: 100% 9000%;
  background-repeat: no-repeat;
  background-position: center top;
  transition: background-position .3s;
}

.play {
  background-position: center bottom;
}
<div class="test">
</div>
vals
  • 61,425
  • 11
  • 89
  • 138
  • i like the idea, even if it's a bit hacky the use of the big value of background-size – Temani Afif Apr 02 '18 at 10:43
  • I noticed that in case of a faster duration, like .3s, the javascript code will get a little bit out of sync with the css animation. Do you know a way to keep them in sync? – Jeanluca Scaljeri Apr 02 '18 at 16:48
  • @JeanlucaScaljeri I have changed the time to .3s as your suggestion, and I can't see the out of sync effect. Can you explain a little bit further what is the effect that you are seeing ? – vals Apr 02 '18 at 18:52
  • @TemaniAfif Than you for your edit ! The big backgroiund size is just to avoid banding in the result . But it would be also a cool effect to do it with smaller sizes – vals Apr 02 '18 at 18:54
  • Sorry, I've messed up, the comment was for the post below. Thans a lot for you solution!! – Jeanluca Scaljeri Apr 02 '18 at 19:57
0

Ok, here is my approach:

  • hold animation, until you once added .enabled (prevents animation on load)
  • toggle an .active class on every click
  • important: define a →different← animation in .active, even if your shake or wobble animation forth and back is identical, define it as a copy. (otherwise there will be no animation on the way back)

const div = document.querySelector('.target')
div.addEventListener('click', (e) => {
  div.classList.add('active');
  div.classList.toggle('play');
})
/* REF https://stackoverflow.com/a/49575979 */
body, html { /* eye candy */
  background: #444; display: flex; min-height: 100vh; align-items: center; justify-content: center;
}

div { /* eye candy */
  width: 200px; height: 100px; border: 12px dashed #888; border-radius: 20px; 
  background: red;
}

/* prevents that there is an animation already on loading */
.active {
  animation: trafficLight 2s linear;
}

.play {
  border: 12px solid white; /* white border == play */
  background: green;
  /* yes, already setting for the way back!
     fun fact: if you do not set a DIFFERENT animation, NO animation whatsoever will happen
    (so also for simple bounce animations, where forth=back would be o.k.
     you do need to clone it... varying time (1.001s) or direction is NOT enough.) */
  animation: trafficLightBack 2s linear;
}

@keyframes trafficLight {
  0% { background-color: green; }  
  50% { background-color: yellow; }  
  100% { background-color: red;}  
}

@keyframes trafficLightBack {
  0% { background-color: red; }  
  50% { background-color: yellow; }  
  100% { background-color: green;}  
}
<div class="target"></div>
Frank N
  • 9,625
  • 4
  • 80
  • 110