andrea
andrea

Reputation: 406

@keyframes animation works only the first time

I have 3 values in the select: rock, paper, scissor.

The first time I choose a value in the select (rock, paper or scissor), the onChange event fires, and the @keyframes animation works for all 3 values.

But the second time I choose any of the values, the animation no longer works. I do not understand why.

The code is this: jsfiddle.net/9efvxph3/83/

body {
  background: radial-gradient(gold, goldenrod);
  font-family: monospace;
  text-align: center;
  font-size: 1.5rem;
}

@keyframes example {
  0% {
    transform: scale(1);
  }

  100% {
    transform: scale(1.3);
  }
}


img {
  padding: 10px;
  height: 100px;
  width: auto;
}

img:hover {
  transform: scale(1.4);
  transition: all 1s;
}
          <select id="choice" onchange="winner()">
            <option value="" disabled selected>choose</option>
            <option value="rock">rock</option>
            <option value="paper">paper</option>
            <option value="scissors">scissors</option>
          </select>

          <img src="https://img.icons8.com/windows/2x/hand-rock.png" class="user-choice">
          <img src="https://img.icons8.com/wired/2x/paper.png" class="user-choice">
          <img src="https://img.icons8.com/ios/2x/hand-scissors.png" class="user-choice">

          <h3 id="result"> </h3>

          <script>
            const type = ["paper", "scissors", "rock"];

            function winner() {
              var index = document.getElementById("choice").selectedIndex;

              var userChooice = document.getElementById("choice").value;
              var PcChooice = type[Math.floor(Math.random() * type.length)];

              document.getElementsByClassName("user-choice").item(index - 1).style = "animation:example 0.5s alternate";

              if (PcChooice == userChooice) {

                document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You tied";

              } else {
                if ((userChooice == "rock") && (PcChooice == "scissors")) {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
                } else if ((userChooice == "paper") && (PcChooice == "rock")) {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
                } else if ((userChooice == "scissors") && (PcChooice == "paper")) {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
                } else {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You loose";
                }
              }
              document.getElementById("choice").selectedIndex = 0;
            };

          </script>

Upvotes: 0

Views: 894

Answers (4)

vineeth pappu
vineeth pappu

Reputation: 542

You can simply fix it by adding and removing classname instead of directly updating the element styles.

change the below line

document.getElementsByClassName("user-choice").item(index - 1).style = "animation:example 0.5s alternate";

To

document.getElementsByClassName("user-choice").item(index - 1).classList.add('animate');
          
setTimeout(() => {
    document.getElementsByClassName("user-choice").item(index - 1).classList.remove('animate');
}, 1000)

Where the setimeout delay timing is based on the animation duration

body {
  background: radial-gradient(gold, goldenrod);
  font-family: monospace;
  text-align: center;
  font-size: 1.5rem;
}

@keyframes example {
  0% {
    transform: scale(1);
  }

  100% {
    transform: scale(1.3);
  }
}


img {
  padding: 10px;
  height: 100px;
  width: auto;
}

img:hover {
  transform: scale(1.4);
  transition: all 1s;
}

.animate {
  animation: example 0.5s alternate;
}
<select id="choice" onchange="winner()">
            <option value="" disabled selected>choose</option>
            <option value="rock">rock</option>
            <option value="paper">paper</option>
            <option value="scissors">scissors</option>
          </select>

          <img src="https://img.icons8.com/windows/2x/hand-rock.png" class="user-choice">
          <img src="https://img.icons8.com/wired/2x/paper.png" class="user-choice">
          <img src="https://img.icons8.com/ios/2x/hand-scissors.png" class="user-choice">

          <h3 id="result"> </h3>

          <script>
            const type = ["paper", "scissors", "rock"];

            function winner() {
              var index = document.getElementById("choice").selectedIndex;

              var userChooice = document.getElementById("choice").value;
              var PcChooice = type[Math.floor(Math.random() * type.length)];

              document.getElementsByClassName("user-choice").item(index - 1).classList.add('animate');
              
              setTimeout(() => {
                document.getElementsByClassName("user-choice").item(index - 1).classList.remove('animate');
              }, 1000)

              if (PcChooice == userChooice) {

                document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You tied";

              } else {
                if ((userChooice == "rock") && (PcChooice == "scissors")) {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
                } else if ((userChooice == "paper") && (PcChooice == "rock")) {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
                } else if ((userChooice == "scissors") && (PcChooice == "paper")) {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
                } else {
                  document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You loose";
                }
              }
              document.getElementById("choice").selectedIndex = 0;
            };

          </script>

Upvotes: 0

Xhynk
Xhynk

Reputation: 13880

This is because you're adding the animation inline once on the first selection, and then never removing it, so it's never invoked again. The easiest way without refactoring everything is to remove the inline style after the animation using the animationend event, or even using something like setTimeout() with a delay longer than the animation.

Simply adding the following somewhere after you've added the animation would work:

document.getElementsByClassName("user-choice").item(index - 1).addEventListener("animationend", function(evt){
    evt.target.style = "";
});

Or, the less elegant:

setTimeout(function(){
    document.getElementsByClassName("user-choice").item(index - 1).style = "";
}.bind(index), 510 );

That should be all you need to "fix the problem", but looking at readability may help a little as well.

To clean things up a bit more - generally if you're requesting an element, nodelist, (or anything really) that's not a static property, you should consider storing it in a variable. Instead of parsing the DOM multiple times for the same thing in the same if/else blocks, you can store it as a variable, and invoke a function (like innerText) once at the end.

This is beneficial to prevent needing to update references in multiple places if you need to make a change.

Lastly, you may be better suited by attaching your keyframe animation to a class, and adding/removing the class with classList.

const type    = ["paper", "scissors", "rock"],
      result  = document.getElementById('result'),
      choice  = document.getElementById('choice'),
      choices = document.getElementsByClassName('user-choice');

function winner(){
    // Prevent changes while the game is "running"
    choice.disabled = true;

    var userChoice = choice.value,
        pcChoice   = type[Math.floor(Math.random() * type.length)],
        selected   = choices[choice.selectedIndex-1];
        status;
    
    if( pcChoice == userChoice ){
        status = 'tied';
    } else {
        if( userChoice == 'rock' && pcChoice == 'scissors' ){
            status = 'win';
        } else if( userChoice == 'paper' && pcChoice == 'rock' ){
            status = 'win';
        } else if( userChoice == 'scissors' && pcChoice == 'paper' ){
            status = 'win';
        } else {
            status = 'lose';
        }
    }

    selected.classList.add('animated');        
    result.innerText = userChoice + ' - ' + pcChoice + ': You ' + status;

    // After the animation, reset everything to its default state
    selected.addEventListener('animationend', function(evt){
        choice.disabled = false;
        choice.selectedIndex = 0;
        evt.target.classList.remove('animated');
    });
};
body {
  background: radial-gradient(gold, goldenrod);
  font-family: monospace;
  text-align: center;
  font-size: 1.5rem;
}

@keyframes example {
  0% { transform: scale(1); }
  100% { transform: scale(1.3); }
}


img {
  padding: 10px;
  height: 100px;
  width: auto;
}

img:hover {
  transform: scale(1.4);
  transition: all 1s;
}

img.animated {
  animation: example 0.5s alternate;
}
<select id="choice" onchange="winner()">
  <option value="" disabled selected>choose</option>
  <option value="rock">rock</option>
  <option value="paper">paper</option>
  <option value="scissors">scissors</option>
</select>

<img src="https://img.icons8.com/windows/2x/hand-rock.png" class="user-choice">
<img src="https://img.icons8.com/wired/2x/paper.png" class="user-choice">
<img src="https://img.icons8.com/ios/2x/hand-scissors.png" class="user-choice">

<h3 id="result"> </h3>


Documentation & Function Reference

Upvotes: 1

Amaresh S M
Amaresh S M

Reputation: 3010

First time when you choose the value in the select onchange event fires and animation style is added. (permanently added in the dom)

Second time when you choose any of the values onchange event will fire but the animation will not work because it is already there in the dom.

Add .animate class onchange.

CSS:

.animate {
  animation: example 0.5s alternate;
}

JS:

document.getElementsByClassName("user-choice").item(index - 1).classList.add('animate');

Before adding .animate class remove .animate class from all .user-choice elements.

var choicess = document.getElementsByClassName("user-choice");
[].forEach.call(choicess, function(el) {
  el.classList.remove('animate');
});

body {
  background: radial-gradient(gold, goldenrod);
  font-family: monospace;
  text-align: center;
  font-size: 1.5rem;
}

.animate {
  animation: example 0.5s alternate;
}

@keyframes example {
  0% {
    transform: scale(1);
  }
  100% {
    transform: scale(1.3);
  }
}

img {
  padding: 10px;
  height: 100px;
  width: auto;
}

img:hover {
  transform: scale(1.4);
  transition: all 1s;
}
<select id="choice" onchange="winner()">
  <option value="" disabled selected>choose</option>
  <option value="rock">rock</option>
  <option value="paper">paper</option>
  <option value="scissors">scissors</option>
</select>

<img src="https://img.icons8.com/windows/2x/hand-rock.png" class="user-choice">
<img src="https://img.icons8.com/wired/2x/paper.png" class="user-choice">
<img src="https://img.icons8.com/ios/2x/hand-scissors.png" class="user-choice">

<h3 id="result"> </h3>

<script>
  const type = ["paper", "scissors", "rock"];

  function winner() {
    var index = document.getElementById("choice").selectedIndex;

    var userChooice = document.getElementById("choice").value;
    var PcChooice = type[Math.floor(Math.random() * type.length)];

    var choicess = document.getElementsByClassName("user-choice");
    [].forEach.call(choicess, function(el) {
      el.classList.remove('animate');
    });

    document.getElementsByClassName("user-choice").item(index - 1).classList.add('animate');

    if (PcChooice == userChooice) {

      document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You tied";

    } else {
      if ((userChooice == "rock") && (PcChooice == "scissors")) {
        document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
      } else if ((userChooice == "paper") && (PcChooice == "rock")) {
        document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
      } else if ((userChooice == "scissors") && (PcChooice == "paper")) {
        document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You win";
      } else {
        document.getElementById("result").innerHTML = userChooice + " - " + PcChooice + ": You loose";
      }
    }
    document.getElementById("choice").selectedIndex = 0;
  };
</script>

Upvotes: 0

araujo.jpc
araujo.jpc

Reputation: 101

Based on your code it's best to end your function by adding and event listener related to the end of the animation, removing the animation from the style of the element.

Add the code below at the end of your winner() function:

document.getElementsByClassName("user-choice").item(index - 1).addEventListener("animationend", function(e) {
  e.target.style = "";
});

Upvotes: 1

Related Questions