0

N.B. With the (more complex) setup I'm actually working with I can't use CSS Transitions. I recognise that CSS Transitions would be a perfectly good solution in the example below.

I'm having a little trouble with

animation-direction: reverse

which I've never used before but doesn't seem to be running the way I might have expected it to.

The easiest solution to my problem would be to write two CSS @keyframes animations and use one or the other.

But for the sake of economy and elegance I would like to use a single animation and play it forwards or backwards.

This example below shows the effect I'm trying to achieve.

When the page loads, pressing either button will fire the intended animation.

However, after one button is pushed, the animation no longer runs and only the end-frame of the forwards or reverse animation is displayed.

What am I doing wrong here?

Working Example:

const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');


buttonOutbound.addEventListener('click', () => {   
  square.className = 'square';   
  square.classList.add('outbound');
}, false);

buttonReturn.addEventListener('click', () => {   
  square.className = 'square';
  square.classList.add('return');
}, false);
.square {
  width: 100px;
  height: 100px;
  margin: 12px;
  background-color: red;
  transform: translateX(0) scale(1);
}

.square.outbound {
  animation: animateSquare 1s linear normal forwards;
}

.square.return {
  animation: animateSquare 1s linear reverse forwards;
}

@keyframes animateSquare {
  
  100% {
    background-color: blue;
    transform: translateX(200px) scale(0.5);
  }
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
Rounin
  • 27,134
  • 9
  • 83
  • 108
  • 1
    related to understand what is happening: https://stackoverflow.com/a/51221329/8620333 / https://stackoverflow.com/a/57333022/8620333 – Temani Afif Mar 22 '21 at 20:56
  • Thanks, @TemaniAfif. The key for me was when I realised that the `.outbound` class alone was maintaining the presentational state of `.square` after the animation had completed - and it was doing so solely on the basis of `animation-fill-mode` value. Consequently, as soon as I removed the `.outbound` class, the single thing holding the presentational state in place... wasn't there anymore. – Rounin Mar 22 '21 at 22:10

2 Answers2

1

The browser considers that animation as complete therefore it does not restart it in order to restart the animation you need to first remove the class and then re-add it however for the browser to recognize this change you need add a slight delay. Adding a setTimeout does the trick even if the timeout is 0 because js is single threaded.

const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');


buttonOutbound.addEventListener('click', () => {   
  square.classList.remove('return');
  square.classList.remove('outbound'); 
  setTimeout(() => {
      square.classList.add('outbound');
  }, 0) 
}, false);

buttonReturn.addEventListener('click', () => {  
  square.classList.remove('return');
  square.classList.remove('outbound'); 
  setTimeout(() => {
      square.classList.add('return');
  }, 0) 
}, false);
.square {
  width: 100px;
  height: 100px;
  margin: 12px;
  background-color: red;
  transform: translateX(0) scale(1);
}

.square.outbound {
  animation: animateSquare 1s linear normal forwards;
}

.square.return {
  animation: animateSquare 1s linear reverse forwards;
}

@keyframes animateSquare {
  
  100% {
    background-color: blue;
    transform: translateX(200px) scale(0.5);
  }
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
Rounin
  • 27,134
  • 9
  • 83
  • 108
Ahsan Naveed
  • 104
  • 7
  • Thanks very much for this attempt, @AhsanNaveed, but I'm not sure this works, does it (?) – Rounin Mar 22 '21 at 19:13
  • @Rounin It seems to be working for me. What issue are you facing? – Ahsan Naveed Mar 24 '21 at 21:42
  • It animates the first time and doesn't animate after that. – Rounin Mar 24 '21 at 23:01
  • Running the above code snippet on stack overflow (using the run code snippet button) seems to work correctly for me. – Ahsan Naveed Mar 25 '21 at 16:21
  • I gave it a spin in Brave and you're right - it's working much better there. (Albeit with a slight flash of styled content when the square is on the right and you press the right-hand button.) But - and I concede you probably haven't seen this - it's not working in Firefox. – Rounin Mar 25 '21 at 23:31
0

I had a think about this away from my laptop screen and realised that... what was missing from my original set up was one additional static class, describing the presentational state of .square after the .outbound animation has run:

.square.outbounded {
  background-color: blue;
  transform: translateX(200px) scale(0.5);
}

(N.B. There's no need for a corresponding static class, describing the state of .square after the .return animation has run, since the presentational state that class would describe is already described in the initial styles of .square)


Working Example (with .outbounded class added)

const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');


buttonOutbound.addEventListener('click', () => {

  square.className = 'square outbound';
  
  setTimeout(() => {
    square.classList.add('outbounded');
    square.classList.remove('outbound');
  }, 1000);
  
}, false);

buttonReturn.addEventListener('click', () => {

  square.className = 'square return';
  
  setTimeout(() => {
    square.classList.remove('return');
  }, 1000);
  
}, false);
.square {
  width: 100px;
  height: 100px;
  margin: 12px;
  background-color: red;
  transform: translateX(0) scale(1);
}

.square.outbounded {
  background-color: blue;
  transform: translateX(200px) scale(0.5);
}

.square.outbound {
  animation: animateSquare 1s linear normal forwards;
}

.square.return {
  animation: animateSquare 1s linear reverse forwards;
}

@keyframes animateSquare {
  
  100% {
    background-color: blue;
    transform: translateX(200px) scale(0.5);
  }
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
Rounin
  • 27,134
  • 9
  • 83
  • 108