Grow your CSS skills. Land your dream job.

Restart CSS Animation

Published by Chris Coyier

With CSS animations (ala @keyframes) it's not as easy as you might think to "restart" it.

Let's say you set it to run once:

.run-animation {
  -webkit-animation: my-fancy-animation 5s 1;
  -moz-animation:    my-fancy-animation 5s 1;
  animation:         my-fancy-animation 5s 1; 
}

You have that run on, say, your logo:

<h1 id="logo" class="run-animation">
  Fancy Logo
</h1>

Then for some reason you want to have that animation run again, perhaps on a click. You might think CSS provides a way to kick it off again, but as far as I know it doesn't. You might even try removing and re-adding that class name via JavaScript (jQuery):

$("#logo").click(function() {
  // not gonna work
  $(this).removeClass("run-animation").addClass("run-animation");
});

If you put a tiny delay between removing and re-adding the class (e.g. setTimeout()), it will work, but I think it goes without saying that that isn't ideal. What you really need to do is remove the element from the page entirely and re-insert it. As a quick demo with jQuery, we'll clone it, insert it right after itself, then remove the original.

$("#logo").click(function() {	
	      	      
 var el     = $(this),  
     newone = el.clone(true);
           
 el.before(newone);
        
 $("." + el.attr("class") + ":last").remove();

});

or the basics with just JavaScript:

var elm = this,
var newone = elm.cloneNode(true);
elm.parentNode.replaceChild(newone, elm);

View Demo

This same problem exists for any type of event. Say you have an animation you want to run immediately, but then again on :hover. Same problem, it won't run again. Oli Studholme has some interesting research on this. One way around it is by having two animations that are exactly the same except for their name, then you can switch which one you use for the :hover event (or whatever) and it will work.

img        {animation: spin-cat-1 2s;}
img:active {animation: spin-cat-2 2s;}
@keyframes spin-cat-1 {50%{transform:rotateY(180deg);}}
@keyframes spin-cat-2 {50%{transform:rotateY(180deg);}}

View Demo

A CSS preprocessor could help make maintaining that easier by importing a chunk of code so you only have to maintain it in once place, but ultimately it's bloat-y. Unfortunately you also cannot define multiple keyframe animation names in one declaration, which would also be useful (and not just for this, think of more complex animations where you could rewrite just certain keyframes over a base keyframe animation).

Oli also played with a variety of other techniques to get it to work, like altering duration, delays, and iterations instead of changing the name, but in my opinion all of those are even worse than changing the name.

Update: Another JavaScript Method to Restart a CSS Animation

Trigger a reflow in between removing and adding the class name. For example:

// retrieve the element
element = document.getElementById("logo");

// reset the transition by...
element.addEventListener("click", function(e) {
  e.preventDefault;
  
  // -> removing the class
  element.classList.remove("run-animation");
  
  // -> triggering reflow /* The actual magic */
  // without this it wouldn't work. Try uncommenting the line and the transition won't be retriggered.
  element.offsetWidth = element.offsetWidth;
  
  // -> and re-adding the class
  element.classList.add("run-animation");
}, false);
Check out this Pen!

Other JavaScript

Just for the record, you can also effect the playing state of a CSS animation through JavaScript, like pausing and restarting it.

element.style.webkitAnimationPlayState="paused";
element.style.webkitAnimationPlayState="running";

Obviously consider the other vendor prefixes properties.

Interestingly, the natural end of a CSS animation doesn't set the play state to "paused". You would have to do that yourself. Say you wanted to restart an animation for every hover and you were cool using jQuery to do it...

$("#thing").hover(function() {
      
  this.style.webkitAnimationPlayState = "running";

  $(this).on('webkitAnimationEnd', function() {
    this.style.webkitAnimationPlayState = "paused";
  });
        
});

Notice how you have to watch for the AnimationEnd event to fire to set it back to "paused" or it won't restart properly.

Comments

  1. Paul

    What are you doing on the last line of code? Are you just trying to remove the element? Couldn’t it be done through el.remove()?

    • You now have two elements with the same class name so using ‘el.remove’ will remove them both so you need specify that it needs to remove the last one.

      I think thats correct ha

    • Paul

      Can’t be, bc el == $(this). It hasn’t been manipulated from what I can see

    • Rafał Krupiński

      Even more simplified:

      $(this).replaceWith($(this).clone());

    • Rafał Krupiński

      $(this).replaceWith($(this).clone(true)); is what I meant

  2. I had a thought about using insertBefore on the element to reinsert it but that didn’t work. One thing though, the demo has the animation on the h1 element not the class, so removing and replacing the class can’t work…

  3. clamster

    To restart the animation, wrap the addClass method with a setTimeout() should do the trick. Something similar to this event handler:

    var elem = this;
    elem.className = '';
    setTimeout(function () {
      elem.className = 'run-animation';
    }, 0);
    • This is also what I think the issue is. Removing and adding a class name in the same execution cycle (your event handler in this instance) will result in nothing changing in the DOM once the JavaScript has yielded.

      The setTimeout will result in the JavaScript yielding between removing the class name and re-adding it, which allows the browser to notice the DOM manipulation and act upon it.

      You don’t really need to wrap the addClass method, you can just create a closure over $(this) within your onclick handler and reference it in your setTimeout call (like the elem variable in clamster’s comment), so you can still use jQuery removeClass and addClass if $(this) is assigned to elem.

    • Jesper Ek

      Another way of doing it using addClass/removeClass is to force a reflow between the calls, which is a bit nicer than setTimeout imho:

      $(this).removeClass('trigger').width(); // reading width() forces reflow 
      $(this).addClass('trigger');
      

      Editor’s note: Jorge Antunes wrote in to say this works great and provided a CodePen demo.

    • Jesper, I agree it’s nicer, but that’s the approach that the article says is not working, isn’t it?

  4. Adam

    If you’re going to use jQuery anyway, why not just to the whole animation in jQuery, which will be cross browser compatible?

    • You certainly could. There are considerations and it depends on real use cases, so it’s hard to debate theoretically. They both can do things each other can’t do. JavaScript based animations have deeper support but CSS animations are generally smoother and can be hardware accelerated.

    • True that, Chriss, but you’re doing vendor specific animations here and hardware accelerated or not, you still need a fallback for browsers that don’t support the animation using CSS, so you might as well do it in jQuery, unless you’ve go the feature detection way and target non-webkit browsers in this case.

    • From what I’ve seen jQuery/Javascript animations are smoother then CSS3 ones…

    • And the debate goes on! This was actually my question also. Why start mixing css animations and javascript animations? Seems like it would be a pain to come back to in the future.

      I think it’ll be easier to stick with one and since CSS isn’t really ready yet (browser support, inability to restart animations), just stick with javascript.

  5. Animation was smooth on Safari and Chrome, but not on IE 8. I haven’t tried on Firefox though.

    • Rafał Krupiński

      I bellieve that’s because IE8 does not support css animations.

  6. Very useful thanks.

  7. Oscar Broman

    Hello,
    I ran into this problem not so long ago, the way I solved it was by removing the class then setting a 0ms timer for re-adding it.

    $('something').removeClass('anim').animate({'nothing':null}, 1, function () {
    $(this).addClass('anim');
    });

    Simple, yet effective!

  8. I will translate the Restart CSS Animation to my language Turkish.

  9. The easiest way to replay css animations from the beginning is if elements are placed in a main wrapper:

    function Replay(){
    var container = document.getElementById(“wrapper”);
    var content = container.innerHTML;
    container.innerHTML= content;
    console.log(“replay”);
    }

  10. Jonas
    Permalink to comment#

    I ran into an ie11 keyframes animation problem and you have the same problem here, basically none of the animations play :(

  11. Kyle
    Permalink to comment#

    I have almost 0 jquery knowledge. What you did above was ACE and perfect only thing is i need it to be timed so e.g. remove the element after 3 seconds and then add it again after 40 seconds. How would i go about doing this?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".