CSS animation-direction not working as I would have expected

260 views Asked by At

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>

2

There are 2 answers

0
Rounin On BEST ANSWER

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>

5
Ahsan Naveed On

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>