GibboK GibboK - 1 month ago 11
CSS Question

CSS3 Keyframe Animation issue with "timelines"

In the following example I have 3 divs they need to be animated using CSS3 Keyframe Animation with the following rules (when I mention hidden/show in the text below actually use opacity 0.1/1 in the CSS):

A) When the page load


  • ladyDefault is shown

  • ladyCorrect is hidden

  • ladyWrong is hidden



B) When User click btnCorrect


  • ladyDefault is hidden

  • ladyCorrect is shown

  • ladyWrong is hidden



C) When User click btnWrong


  • ladyDefault is hidden

  • ladyCorrect is hidden

  • ladyWrong is shown



Currently:


  • Sequence case A) and B) work fine.

  • Sequence case A) and C) work fine.



But I am not able to fix the following cases:


  • Sequence case A) and B) and C) does not work(ladyWrong is not shown).

  • Sequence case A) and C) and B) does not work (ladyCorrect is not shown).



I would like to know how to fix it possibly using only CSS 3.

Test the following example using only Google Chrome.



var btnCorrect = document.getElementById('btnCorrect');
var btnWrong = document.getElementById('btnWrong');
var ladyDefault = document.getElementById('ladyDefault');
var ladyCorrect = document.getElementById('ladyCorrect');
var ladyWrong = document.getElementById('ladyWrong');

btnCorrect.addEventListener('click', function(event) {
ladyDefault.classList.add('hideLadyDefault');
ladyWrong.classList.add('hideLadyWrong');
ladyCorrect.classList.add('showLadyCorrect');
});
btnWrong.addEventListener('click', function(event) {
ladyDefault.classList.add('hideLadyDefault');
ladyCorrect.classList.add('hideLadyCorrect');
ladyWrong.classList.add('showLadyWrong');
});

#ladyDefault,
#ladyCorrect,
#ladyWrong {
width: 100px;
height: 150px;
display: inline-block;
margin: 5px;
}

#ladyDefault {
background-color: blue;
}

#ladyCorrect {
background-color: green;
opacity: 0.1;
}

#ladyWrong {
background-color: red;
opacity: 0.1;
}

#btnCorrect,
#btnWrong {
height: 50px;
width: 100px;
display: inline-block;
margin: 5px;
}

#btnCorrect {
background-color: lime;
}

#btnWrong {
background-color: darkred;
}
/*
--------------------------- lady default
*/
@keyframes hideLadyDefault {
0% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}

.hideLadyDefault {
animation-name: hideLadyDefault;
animation-duration: 0;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
animation-delay: 0;
}
/*
--------------------------- lady correct
*/
@keyframes showLadyCorrect {
0% {
opacity: 0.1;
}
100% {
opacity: 1;
}
}

.showLadyCorrect {
animation-name: showLadyCorrect;
animation-duration: 1s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
animation-delay: 0;
}

@keyframes hideLadyCorrect {
0% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}

.hideLadyCorrect {
animation-name: hideLadyCorrect;
animation-duration: 0;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: both
animation-delay: 0;
}
/*
--------------------------- lady wrong
*/
@keyframes showLadyWrong {
0% {
opacity: 0.1;
}
100% {
opacity: 1;
}
}

.showLadyWrong {
animation-name: showLadyWrong;
animation-duration: 1s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
animation-delay: 0;
}

@keyframes hideLadyWrong {
0% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}

.hideLadyWrong {
animation-name: hideLadyWrong;
animation-duration: 0;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: both;
animation-delay: 0;
}

<div id="ladyDefault">ladyDefault</div>
<div id="ladyCorrect">ladyCorrect</div>
<div id="ladyWrong">ladyWrong</div>

<div id="btnCorrect">btnCorrect</div>
<div id="btnWrong">btnWrong</div>




Answer

Original Answer:

You need to remove the previous classes also before adding the new ones. When you don't remove the other class before adding a new one, each of them try to set an animation on the same element and as is always the case with CSS, the selector (class) which is defined later in the CSS wins.

var btnCorrect = document.getElementById('btnCorrect');
var btnWrong = document.getElementById('btnWrong');
var ladyDefault = document.getElementById('ladyDefault');
var ladyCorrect = document.getElementById('ladyCorrect');
var ladyWrong = document.getElementById('ladyWrong');

btnCorrect.addEventListener('click', function(event) {
  ladyDefault.classList.add('hideLadyDefault');
  ladyWrong.classList.remove('showLadyWrong');
  ladyWrong.classList.add('hideLadyWrong');
  ladyCorrect.classList.remove('hideLadyCorrect');
  ladyCorrect.classList.add('showLadyCorrect');
});
btnWrong.addEventListener('click', function(event) {
  ladyDefault.classList.add('hideLadyDefault');
  ladyCorrect.classList.remove('showLadyCorrect');
  ladyCorrect.classList.add('hideLadyCorrect');
  ladyWrong.classList.remove('hideLadyWrong');
  ladyWrong.classList.add('showLadyWrong');
});
#ladyDefault,
#ladyCorrect,
#ladyWrong {
  width: 100px;
  height: 150px;
  display: inline-block;
  margin: 5px;
}
#ladyDefault {
  background-color: blue;
}
#ladyCorrect {
  background-color: green;
  opacity: 0.1;
}
#ladyWrong {
  background-color: red;
  opacity: 0.1;
}
#btnCorrect,
#btnWrong {
  height: 50px;
  width: 100px;
  display: inline-block;
  margin: 5px;
}
#btnCorrect {
  background-color: lime;
}
#btnWrong {
  background-color: darkred;
}
/*
--------------------------- lady default
*/

@keyframes hideLadyDefault {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0.1;
  }
}
.hideLadyDefault {
  animation-name: hideLadyDefault;
  animation-duration: 0;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-delay: 0;
}
/*
--------------------------- lady correct
*/

@keyframes showLadyCorrect {
  0% {
    opacity: 0.1;
  }
  100% {
    opacity: 1;
  }
}
.showLadyCorrect {
  animation-name: showLadyCorrect;
  animation-duration: 1s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-delay: 0;
}
@keyframes hideLadyCorrect {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0.1;
  }
}
.hideLadyCorrect {
  animation-name: hideLadyCorrect;
  animation-duration: 0;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: both animation-delay: 0;
}
/*
--------------------------- lady wrong
*/

@keyframes showLadyWrong {
  0% {
    opacity: 0.1;
  }
  100% {
    opacity: 1;
  }
}
.showLadyWrong {
  animation-name: showLadyWrong;
  animation-duration: 1s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-delay: 0;
}
@keyframes hideLadyWrong {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0.1;
  }
}
.hideLadyWrong {
  animation-name: hideLadyWrong;
  animation-duration: 0;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: both;
  animation-delay: 0;
}
<div id="ladyDefault">ladyDefault</div>
<div id="ladyCorrect">ladyCorrect</div>
<div id="ladyWrong">ladyWrong</div>

<div id="btnCorrect">btnCorrect</div>
<div id="btnWrong">btnWrong</div>


Solution with only one keyframe rule:

You can also achieve the same effect with a single @keyframes rule instead of using multiple. All that would be needed is to set the animation-direction as reverse for the hide. But the old class must still be removed because once an animation is set on the element, the UA remembers its execution as mentioned in this answer of mine.

var btnCorrect = document.getElementById('btnCorrect');
var btnWrong = document.getElementById('btnWrong');
var ladyDefault = document.getElementById('ladyDefault');
var ladyCorrect = document.getElementById('ladyCorrect');
var ladyWrong = document.getElementById('ladyWrong');

btnCorrect.addEventListener('click', function(event) {
  ladyDefault.classList.add('hideLadyDefault');
  ladyWrong.classList.remove('showLadyWrong');
  ladyWrong.classList.add('hideLadyWrong');
  ladyCorrect.classList.remove('hideLadyCorrect');
  ladyCorrect.classList.add('showLadyCorrect');
});
btnWrong.addEventListener('click', function(event) {
  ladyDefault.classList.add('hideLadyDefault');
  ladyCorrect.classList.remove('showLadyCorrect');
  ladyCorrect.classList.add('hideLadyCorrect');
  ladyWrong.classList.remove('hideLadyWrong');
  ladyWrong.classList.add('showLadyWrong');
});
#ladyDefault,
#ladyCorrect,
#ladyWrong {
  width: 100px;
  height: 150px;
  display: inline-block;
  margin: 5px;
}
#ladyDefault {
  background-color: blue;
}
#ladyCorrect {
  background-color: green;
  opacity: 0.1;
}
#ladyWrong {
  background-color: red;
  opacity: 0.1;
}
#btnCorrect,
#btnWrong {
  height: 50px;
  width: 100px;
  display: inline-block;
  margin: 5px;
}
#btnCorrect {
  background-color: lime;
}
#btnWrong {
  background-color: darkred;
}
/*
--------------------------- lady default
*/

.hideLadyDefault {
  animation-name: show;
  animation-duration: 0;
  animation-iteration-count: 1;
  animation-direction: reverse;
  animation-fill-mode: forwards;
  animation-delay: 0;
}
/*
--------------------------- lady correct
*/

@keyframes show {
  0% {
    opacity: 0.1;
  }
  100% {
    opacity: 1;
  }
}
.showLadyCorrect {
  animation-name: show;
  animation-duration: 1s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-delay: 0;
}
.hideLadyCorrect {
  animation-direction: reverse;
}
/*
--------------------------- lady wrong
*/

.showLadyWrong {
  animation-name: show;
  animation-duration: 1s;
  animation-iteration-count: 1;
  animation-direction: normal;
  animation-fill-mode: forwards;
  animation-delay: 0;
}
.hideLadyWrong {
  animation-direction: reverse;
}
<div id="ladyDefault">ladyDefault</div>
<div id="ladyCorrect">ladyCorrect</div>
<div id="ladyWrong">ladyWrong</div>

<div id="btnCorrect">btnCorrect</div>
<div id="btnWrong">btnWrong</div>


Solution with transition: (might be better fit)

The better option would be to use transition if you could instead of animation. Here you could just set the opacity via inline style attributes and let the transition property handle the rest. Unlike the animations, transitions can execute the reverse state by default. (This is the best bet if it only a simple change of opacity from 0.1 to 1, if it is a complex multi-step change the transitions are out of scope.)

var btnCorrect = document.getElementById('btnCorrect');
var btnWrong = document.getElementById('btnWrong');
var ladyDefault = document.getElementById('ladyDefault');
var ladyCorrect = document.getElementById('ladyCorrect');
var ladyWrong = document.getElementById('ladyWrong');

btnCorrect.addEventListener('click', function(event) {
  ladyDefault.style["opacity"] = "0.1";
  ladyWrong.style["opacity"] = "0.1";
  ladyCorrect.style["opacity"] = "1";
});
btnWrong.addEventListener('click', function(event) {
  ladyDefault.style["opacity"] = "0.1";
  ladyCorrect.style["opacity"] = "0.1";
  ladyWrong.style["opacity"] = "1";
});
#ladyDefault,
#ladyCorrect,
#ladyWrong {
  width: 100px;
  height: 150px;
  display: inline-block;
  margin: 5px;
}
#ladyDefault {
  background-color: blue;
  transition: all 1s ease;
}
#ladyCorrect {
  background-color: green;
  opacity: 0.1;
  transition: all 1s ease;
}
#ladyWrong {
  background-color: red;
  opacity: 0.1;
  transition: all 1s ease;
}
#btnCorrect,
#btnWrong {
  height: 50px;
  width: 100px;
  display: inline-block;
  margin: 5px;
}
#btnCorrect {
  background-color: lime;
}
#btnWrong {
  background-color: darkred;
}
<div id="ladyDefault">ladyDefault</div>
<div id="ladyCorrect">ladyCorrect</div>
<div id="ladyWrong">ladyWrong</div>

<div id="btnCorrect">btnCorrect</div>
<div id="btnWrong">btnWrong</div>