Proper way of reversing an animation with Vanilla JS?

Viewed 440

So I have a pretty straightforward example here where I apply an animation through JS to grow my inner div. However, upon trying to reverse the animation with JS, I am finding it does not transition; rather, it suddenly snaps back to the original state.

Example:

const grow = document.querySelector('.grow');
const reverse = document.querySelector('.reverse');
const inner = document.querySelector('.inner');


grow.addEventListener('click', function() {
  inner.classList.add('grow');
})

//things I've tried

// reverse.addEventListener('click', function() {
//   inner.classList.add('reverse');
// })

reverse.addEventListener('click', function() {
  inner.style.animationDirection = 'reverse';
})
html, body {
  height: 100%;
  width: 100%;
  margin: 0;
}

.container {
  height: 100%;
  width: 100%;
  background: orange;
  position: relative;
}

.inner {
  position: absolute;
  bottom: 0;
  height: 52px;
  width: 80%;
  background: #fff;
}

.grow {
  animation: grow forwards 1s ease-in-out;
}

.reverse {
  animation: grow reverse 1s ease-in-out;
}

@keyframes grow {
  from {
    height: 52px;
  }
  
  to {
    height: 100%;
  }
}
<div class="container">
  <div class="inner">
    <button class="grow">Grow</button>
    <button class="reverse">Reverse</button>
  </div>
</div>

Can someone explain to me why this is happening, and how I can revert the transition back smoothly without adding a separate keyframe labeled 'shrink'?

2 Answers

It's because both .grow and .reverse attached to .inner and that's messing things up. You have to remove .grow before adding .reverse. However, this time when you remove .grow animation, the div's height will decrease to 52px again, so you must keep its height before you apply .reverse. However(again), you need to decrease .inner's height after running .reverse, otherwise it won't lose its height. So you can do something like this:

reverse.addEventListener('click', function() {
  inner.classList.remove('grow');
  inner.style.height = '100%';

  // You need setTimeout() idk why
  setTimeout(function() {
    inner.classList.add('reverse');
    inner.style.height = '52px';
  }, 10);
})

Sadly animationDirection property only has an effect on infinite animations (source), so you might still want to consider using shrink.

Your code with infinite animations:

const grow = document.querySelector('.grow');
const reverse = document.querySelector('.reverse');
const inner = document.querySelector('.inner');


grow.addEventListener('click', function() {
  inner.classList.add('grow');
})

//things I've tried

// reverse.addEventListener('click', function() {
//   inner.classList.add('reverse');
// })

reverse.addEventListener('click', function() {
  inner.style.animationDirection = 'reverse';
})
html, body {
  height: 100%;
  width: 100%;
  margin: 0;
}

.container {
  height: 100%;
  width: 100%;
  background: orange;
  position: relative;
}

.inner {
  position: absolute;
  bottom: 0;
  height: 52px;
  width: 80%;
  background: #fff;
}

.grow {
  animation: grow forwards 1s infinite;
}

.reverse {
  animation: grow reverse 1s infinite;
}

@keyframes grow {
  from {
    height: 52px;
  }
  
  to {
    height: 100%;
  }
}
<div class="container">
  <div class="inner">
    <button class="grow">Grow</button>
    <button class="reverse">Reverse</button>
  </div>
</div>

Shrink solution:

const grow = document.querySelector('.grow');
const reverse = document.querySelector('.reverse');
const inner = document.querySelector('.inner');


grow.addEventListener('click', function() {
  inner.classList.add('grow');
})

//things I've tried

 reverse.addEventListener('click', function() {
   inner.classList.add('reverse');
 })
html, body {
  height: 100%;
  width: 100%;
  margin: 0;
}

.container {
  height: 100%;
  width: 100%;
  background: orange;
  position: relative;
}

.inner {
  position: absolute;
  bottom: 0;
  height: 52px;
  width: 80%;
  background: #fff;
}

.grow {
  animation: grow forwards 1s ease-in-out;
}

.reverse {
  animation: shrink forwards 1s ease-in-out;
}

@keyframes grow {
  from {
    height: 52px;
  }
  
  to {
    height: 100%;
  }
}

@keyframes shrink {
  from {
    height: 100%;
  }
  
  to {
    height: 52px;
  }
}
<div class="container">
  <div class="inner">
    <button class="grow">Grow</button>
    <button class="reverse">Reverse</button>
  </div>
</div>

Related