Refire CSS animation with javascript after a previous one is complete

拟墨画扇 提交于 2019-11-29 23:54:39

问题


I was trying to use the new animate() function to refire a CSS animation. I'm using Chrome 48 but I wonder if this is even possible right now.

The animation should be just a growing and shrinking square. I've tried several things, as you can see, but none worked:

var elem = document.querySelector(".box");

document.addEventListener("click", function() {
  elem.classList.add("close");
  elem.animate(); //not working
  elem.animate("grow"); //not working
  elem.animate([{
    transform: 'scale(1)'
  }, {
    transform: 'scale(0)'
  }], 1500); //not working
});
.box {
  width: 200px;
  height: 200px;
  background: #099;
  animation: grow 1s ease;
  transform-origin: 0 0;
}
.close {
  animation: grow 1s ease reverse;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<div class="box"></div>

The problem seems to be that when the first animation ends, the animation state of the element is "complete" (or something like that) so when you change the animation property, it is still complete, and it doesn't begin again. As the second animation should fire after an event, it can't be iterated.

Edit: I noticed that this works when if I click while the square is still growing!

Edit2: I found out that if you just add the reverse property, the animation doesn't fire but if you change the time to a different value, or the animation name, it fires again!


回答1:


What you had mentioned in the last paragraph of your question is spot on. Once an animation is set to an element and has executed completely, the browser/UA would continue to remember its state. That animation cannot be restarted (either in the same or reverse direction) unless the original animation is removed atleast for a split second.

For this, we need to introduce a delay (time-out) or force a repaint because without that if we remove and add just the classes alone within the same blocking call then UA would not see any difference. It would continue to think that the animation was never removed.

For re-firing the same animation:

To re-fire the same animation again when the a click is made anywhere in the document, just remove the existing animation's class and add it back again after a delay like in the below snippet.

var elem = document.querySelector(".box");

document.addEventListener("click", function(e) {
  refireAnim("open");
});

function refireAnim(anim) {
  elem.classList.remove(anim);
  setTimeout(function() {
    elem.classList.add(anim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<div class="box open"></div>

For re-firing the same animation in different direction:

To re-fire the same animation again but in a different direction, remove and add the animation's class after a delay like in the above snippet and also change the direction.

var elem = document.querySelector(".box")
  forward = true;

document.addEventListener("click", function() {
  refireAnim("open");
  if (forward) { /* change the direction based on state */
  	elem.style["animation-direction"] = "reverse"; 
  }
  else {
  	elem.style["animation-direction"] = null;
  }  
  forward = !forward; /* change the state */
});

function refireAnim(anim) {
  elem.classList.remove(anim);
  setTimeout(function() {
    elem.classList.add(anim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class="box open"></div>

Or as you pointed out, force a repaint after the removal of the class but before its adding it again. (This seems to avoid the flash on toggle)

var elem = document.querySelector(".box")
  forward = true;

document.addEventListener("click", function() {
  refireAnim("open");
  if (forward) { /* change the direction based on state */
  	elem.style["animation-direction"] = "reverse"; 
  }
  else {
  	elem.style["animation-direction"] = null;
  }  
  forward = !forward; /* change the state */
});

function refireAnim(anim) {
  elem.classList.remove(anim);
  elem.clientHeight;
  elem.classList.add(anim);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class="box open"></div>

For toggling one animation to another:

Create two classes where one is for the open animation, another is for the close animation and toggle them on click such that there is a delay between removal of one class and addition of another.

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

trigger.addEventListener("click", function() {
  currState = elem.className.split(" ")[1];
  newState = (currState == "open") ? "close" : "open";
  switchAnim(currState, newState);
});

function switchAnim(existingAnim, newAnim) {
  elem.classList.remove(existingAnim);
  setTimeout(function() {
    elem.classList.add(newAnim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
.close {
  animation: grow 1s ease reverse forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<input type="checkbox" id="toggle" />
<div class="box open"></div>

As you had pointed out in comments, you could avoid the time-out and instead force the browser to do a repaint before adding the other animation's class also. (This seems to avoid the flash on toggle)

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

trigger.addEventListener("click", function() {
  currState = elem.className.split(" ")[1];
  newState = (currState == "open") ? "close" : "open";
  switchAnim(currState, newState);
});

function switchAnim(existingAnim, newAnim) {
  elem.classList.remove(existingAnim);
  elem.clientHeight;
  elem.classList.add(newAnim);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
.close {
  animation: grow 1s ease reverse forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<input type="checkbox" id="toggle" />
<div class="box open"></div>

The other way to avoid that flash (a brief period where the element reverts to its real height and width before being animated again) is to make use of transitions like in the below snippet.

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

trigger.addEventListener("click", function() {
  elem.classList.toggle("close");
  elem.classList.toggle("open");
});


window.onload = function() {
  elem.classList.toggle("close");
  elem.classList.toggle("open");
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
  transition: all 1s;
}
.open {
  transform: scale(1);
}
.close {
  transform: scale(0);
}
<input type="checkbox" id="toggle" />
<div class="box close"></div>

Re-firing whatever animation exists on the element at a given point:

The checkbox toggles the animation but whenever the document is clicked, whichever animation that is applicable depending on the checkbox's state will be re-fired.

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

document.addEventListener("click", function(e) {
  if (e.target != trigger) {
    currState = elem.className.split(" ")[1];
    switchAnim(currState, currState);
  }
})

trigger.addEventListener("click", function() {
  if (trigger.checked)
    switchAnim("close", "open");
  else
    switchAnim("open", "close");
});

function switchAnim(existingAnim, newAnim) {
  console.log(existingAnim);
  elem.classList.remove(existingAnim);
  setTimeout(function() {
    elem.classList.add(newAnim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
.close {
  animation: grow 1s ease reverse forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<input type="checkbox" id="toggle" checked/>
<div class="box open"></div>



回答2:


  1. Don't use settimeout, use the delay value
  2. Don't mix CSS animations and JS animations, if you don't know what you are doing
  3. Use an iterations value
  4. Use the transform so, that they form a loop. Startvalue equals endvalue. Otherwise you will get steps (golden animation box2)

var elem1 = document.querySelector(".box1");
var elem2 = document.querySelector(".box2");

elem1.animate([{
    transform: 'scale(0)'
  }, {
    transform: 'scale(1)'
  }, {
    transform: 'scale(0)'
  }], {
    duration: 1500,               
    iterations: 9999,
    delay: 0                    
});

elem2.animate([{
    transform: 'scale(0)'
  }, {
    transform: 'scale(1)'
  }], {
    duration: 1500,               
    iterations: 9999,
    delay: 0                    
});
.box {
  width: 200px;
  height: 200px;
  background: #099;
  animation: grow 1s ease;
  transform-origin: 0 0;
}

.box2 {
  background: gold;
}
<div class="box box1"></div>
<div class="box box2"></div>



回答3:


Try this....

HTML

  <body>
    <div id="square"></div>
  </body>

JS

$("document").ready(function(){
  var elem = document.querySelector("#square");


  elem.classList.add("box");


  setInterval(function(){
  elem.classList.remove("box");
   elem.classList.add("boxSh");

  },1000)

});

CSS

 .box {
      width: 200px;
      height: 200px;
      background: #099;
      animation: grow 1s ease;
      transform-origin: 0 0;
    }
    .boxSh {
      width: 200px;
      height: 200px;
      background: #099;
      animation: shrink 1s ease;
      transform-origin: 0 0;
    }
    .close {
      animation: grow 1s ease reverse;
    }
    .sh{
      animation: shrink 1s ease reverse;
    }
    @keyframes grow {
      from {
        transform: scale(0);
      }
      to {
        transform: scale(1);
      }
    }

    @keyframes shrink {

      to {
        transform: scale(0);
      }
    }

DEMO



来源:https://stackoverflow.com/questions/35200605/refire-css-animation-with-javascript-after-a-previous-one-is-complete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!