Code Apprentice
Code Apprentice

Reputation: 33

Transition to default class upon removal of CSS class with animation

I am animating a button. A class is assigned depending on my app state. This is actually implemented in Svelte as follows:

<div class="default"
     class:run-animation="{$animate === true}">
But the equivalent in vanilla javascript without Svelte is:

let element = document... (find element)
element.classList.add("run-animation")
... later ...
element.classList.remove("run-animation)
For the sake of a minimum reproducible example, the classes I'm trying to animate/transition between look like this:

.default {
    top: 20px;
    color: white;
}

@keyframes button-animation {
  from {
    top: 20px;
    color: white;
  }
  20% {
    top: 23px;
    color: white;
  }
  25% {
    color: red;
  }
  100% {
    top: 23px;
    color: red;
  }
}

.run-animation {
  animation-name: button-animation;
  animation-duration: 2s;
  /* Preserve the effect of the animation at ending */
  animation-fill-mode: forwards;
}

I add the class to the element, and the button animates just like I want it to. My problem arises when I remove the class. I want the button to transition smoothly back to the default CSS. I have tried adding the animation to the run-animate class:

.run-animation {
  animation-name: ... ;
  
  top: 23px;
  color: red;
}

I have come across many people stating the transition upon class removal will apply if I add a transition property to the default class. I have tried this as follows:

.default {
  ...
  transition: all 3s linear;
}
But it isn't working. The animation runs smoothly when it is added but the styling immediately reverts to the default when the class is removed (no smooth transition).

MY GOAL: I want to smoothly transition away from the end-state of the animation to the default class when the animate class is removed. Is this possible?

Ideally, I'm adding the class with the Svelte logic at the top so the animation should not be triggered in javascript but rather naturally occur as a result of class assignment.

(My code in practice is a little more complicated than shown, the button has another class with styles not being animated at all and the animation includes more styles such as box-shadow and text-shadow. Still, I don't see why this should be more problematic than just color and top included above)

// JS only toggles '.animation'
document.querySelector("button").addEventListener("click", () => {
  document.querySelector("div.default").classList.toggle("animation");
});
body {display: flex}
button {position: absolute; left: 120px}
div.default {
  position: absolute;
  width: 100px;
  height: 100px;
  background: darkgreen;
}
/* Above code to make a visible working example */

div.default {
  top: 20px;
  color: white;
  transition: top 0.4s, color 0.1s 0.4s;
}

@keyframes define-animation {
  from {
    top: 20px;
    color: white;
  }
}

div.default.animation {
  animation-name: define-animation;
  animation-duration: 2s;
  
  top: 24px;
  color: red;
}
<div class="default">I'm colourful</div>
<button>Toggle ".animation"-class</button>

Above is a working snippet with an animation running on class addition and no reverse transition on class removal. I have tried setting animation direction to opposite values in .default and .animation. I have tried defining the .animate end state properties in the class and/or in the keyframes to attributes.

EDIT: It works now! How? You cannot apply: animation-fill-mode: forwards; The end-attributes need to be defined in the animate class not in the keyframe.

The animation plays when the class is added. Transition timings are used when the class is removed (if the animation has completed).

Upvotes: 3

Views: 2325

Answers (1)

Oskar Grosser
Oskar Grosser

Reputation: 3434

To get a transition effect, you can use the transition-property.
The transition-property can be used here, since every property you want to animate only has a start- and end-value.

Translating animation-percentages to seconds
To translate the percentages of your CSS Animation button-animation to seconds, you just calculate 'percentage' * 'animation-duration'.
This works for both the transition-duration-property as well as for the transition-delay-property.

Example:
color is being animated from 20% to 25%, which is a duration of 5% with a delay of 20%.
All in all, the animation should take 2 seconds. So we calculate for:

  • transition-duration: 5% * 2s = 0.1s
  • transition-delay: 20% * 2s = 0.4s

With that, we can add transition: color 0.1s 0.4s to the .default-class.

Why add it to .default, and not to .animation?
If we were to add the transition-property to .animation, the following would happen:

  • When adding .animation, there will be a transition-effect, since the element now has a transition-property defined.
  • But when removing .animation, the element would no longer have a transition-property defined, meaning there would be no transition.

Now, we want to transition on both adding and removing .animation, meaning we want to have a transition-property defined both when .animation is present and when it is not. That means, transition should not be defined in .animation.

// JS only toggles '.animation'
document.querySelector("button").addEventListener("click", () => {
  document.querySelector("div.default").classList.toggle("animation");
});
body {display: flex}
button {align-self: center}
div.default {
  position: relative;
  border: 2px solid black;
  width: 100px;
  height: 100px;
  background: darkgreen;
}
/* Above code to make a visible working example */

div.default {
  top: 20px;
  color: white;
  transition: top 0.4s, color 0.1s 0.4s;
}
div.default.animation {
  top: 23px;
  color: red;
}
<div class="default">Some text to see the "color"-property</div>
<button>Toggle ".animation"-class</button>

Why does it behave differently...
...when placing the properties inside the to-section of the animation, than when placing them inside .animation itself?

That is, because the properties are not directly applied to the element itself, but rather the element is stopped in its animation (right at the very end), giving only the appearance of the properties being actually applied.
Removing animation-fill-mode: forwards shows the actually applied properties after the animation has played. Those actually applied properties will be the start-values for transition after .animation is removed.

When defining these properties in .animation, they will inherently be the to-values for the animation (if not defined otherwise in animation itself), and be the applied properties of the element.
That means, when removing .animation, the transition will start from there.

Upvotes: 3

Related Questions