0

I am trying to have a animation play when an element is selected, and the reverse animation play when the element is deselected. My CSS looks like this:

@keyframes scale-effect {
    from {
        transform: scale(1);
    }

    to {
        transform: scale(0.75);
    }
}

.card.active {
    animation: 1s ease-in-out reverse scale-effect;
}

.card.inactive {
    animation: 1s ease-in-out forwards scale-effect;
    transform: scale(0.75);

}

This plays the correct animations on page load. However, if I try to change the selected class in javascript:

newActiveObject.classList.remove('inactive');
oldActiveObject.classList.remove('active');
oldActiveObject.classList.add('inactive');
newActiveObject.classList.add('active');

Now, the classes get added properly, and I can see the size changes. However, no animation plays.

Things I have tried:

  • Using setTimeout(..., 0), between removing and adding classes does not have any effect. Same for 1.
  • Using setTimeout(..., 10), works, but very clear ugly fash of post-animation style before animation starts
  • Putting the animation in .card, instead of .inactive. This seems to just disable all animation even on page load.
  • Using getComputedStyle to force DOM redraw in between removing and adding classes. Has no effect.

Does anyone know how to properly replace a animation after removing a class?

mousetail
  • 7,009
  • 4
  • 25
  • 45

3 Answers3

2

Why don't you use transition instead? With animation, it's hard to control the flow. You need either to use a 2nd animation (one for active, one for inactive) or to trigger a reflow in JavaScript (see similar question). Here an example with transition:

const $cards = Array.from(document.getElementsByClassName("card"))

function handleClick(evt) {
  $cards.forEach($card => $card.classList.remove("active"))
  evt.target.classList.add("active")
}

$cards.forEach($card => $card.addEventListener("click", handleClick))
.card {
  display: inline-block;
  margin: 10px;
  width: 100px;
  height: 100px;
  border: 1px solid gray;
  cursor: pointer;
  transition: transform 1s ease-in-out;
  transform: scale(0.75);
}

.card.active {
  transform: scale(1);
}
<div class="card">CARD 1</div>
<div class="card">CARD 2</div>
<div class="card">CARD 3</div>
<div class="card">CARD 4</div>

If your really need animation, here a working example with 2 animations:

const $cards = Array.from(document.getElementsByClassName("card"))

function handleClick(evt) {
  $cards.forEach($card => {
    $card.classList.remove("active")
    $card.classList.add("inactive")
  })
  
  evt.target.classList.remove("inactive")
  evt.target.classList.add("active")
}

$cards.forEach($card => $card.addEventListener("click", handleClick))
@keyframes scale-effect-out {
  from { transform: scale(1); }
  to { transform: scale(0.75); }
}

@keyframes scale-effect-in {
  from { transform: scale(0.75); }
  to { transform: scale(1); }
}

.card {
  display: inline-block;
  margin: 10px;
  width: 100px;
  height: 100px;
  border: 1px solid gray;
  cursor: pointer;
}

.card.inactive {
  animation: 1s forwards ease-in-out scale-effect-out;
}

.card.active {
  animation: 1s forwards ease-in-out scale-effect-in;
}
<div class="card inactive">CARD 1</div>
<div class="card inactive">CARD 2</div>
<div class="card inactive">CARD 3</div>
<div class="card inactive">CARD 4</div>
soywod
  • 4,377
  • 3
  • 26
  • 47
1

I have faced this before, you need to reset the animation for the target element, before adding another animation by javascript.

 var el = document.getElementById('target_element_id');
 el.style.animation = 'none'; // clear animation
 el.offsetHeight; /* trigger reflow */

and then you can add another animation/class. If needed try css '!important' while changing animation.

Shakti
  • 723
  • 8
  • 15
-1

Assuming the active card should start on .75, then get bigger.

.card.active {
    transform: scale(0.75); /* moved this to here */
    animation: 1s ease-in-out reverse scale-effect;
}

.card.inactive {
    animation: 1s ease-in-out forwards scale-effect;
}
Kyle
  • 302
  • 2
  • 5