80

So, I've got this -webkit-animation rule:

@-webkit-keyframes shake {
    0% {
        left: 0;
    }
    25% {
        left: 12px;
    }
    50% {
        left: 0;
    }
    75% {
        left: -12px;
    }
    100% {
        left:0;
    }
}

And some CSS defining some of the animation rules on my box:

#box{
    -webkit-animation-duration: .02s;
    -webkit-animation-iteration-count: 10;
    -webkit-animation-timing-function: linear;
}

I can shake the #box like this:

document.getElementById("box").style.webkitAnimationName = "shake";

But I can't shake it again later.

This only shakes the box once:

someElem.onclick = function(){
    document.getElementById("box").style.webkitAnimationName = "shake";
}

How can I re-trigger a CSS animation via JavaScript without using timeouts or multiple animations?

Paul D. Waite
  • 96,640
  • 56
  • 199
  • 270
David Murdoch
  • 87,823
  • 39
  • 148
  • 191
  • is jQuery or a similar library an option? You'd probably see better browser support. – Surreal Dreams Jan 25 '11 at 19:04
  • jQuery is available but not really applicable in my case. I don't need other browser support as this will be running in Adobe Air only. I'm trying to stick with css animations instead of javascript. – David Murdoch Jan 25 '11 at 19:07
  • 1
    Relevant: [Firmin - JavaScript animation library using CSS transforms and transitions](http://extralogical.net/projects/firmin/) – a paid nerd Nov 22 '11 at 07:44
  • [css-tricks article](https://css-tricks.com/restart-css-animation/) – Blazemonger Oct 22 '15 at 14:07
  • For all browsers: see [javascript - Restart animation in CSS3: any better way than removing the element? - Stack Overflow](https://stackoverflow.com/questions/6268508/restart-animation-in-css3-any-better-way-than-removing-the-element) – user202729 Jan 28 '19 at 02:07

9 Answers9

97

I found the answer based on the source code and examples at the CSS3 transition tests github page.

Basically, CSS animations have an animationEnd event that is fired when the animation completes.

For webkit browsers this event is named “webkitAnimationEnd”. So, in order to reset an animation after it has been called you need to add an event-listener to the element for the animationEnd event.

In plain vanilla javascript:

var element = document.getElementById('box');

element.addEventListener('webkitAnimationEnd', function(){
    this.style.webkitAnimationName = '';
}, false);

document.getElementById('button').onclick = function(){
    element.style.webkitAnimationName = 'shake';
    // you'll probably want to preventDefault here.
};

and with jQuery:

var $element = $('#box').bind('webkitAnimationEnd', function(){
    this.style.webkitAnimationName = '';
});

$('#button').click(function(){
    $element.css('webkitAnimationName', 'shake');
    // you'll probably want to preventDefault here.
});

The source code for CSS3 transition tests (mentioned above) has the following support object which may be helpful for cross-browser CSS transitions, transforms, and animations.

Here is the support code (re-formatted):

var css3AnimationSupport = (function(){
    var div = document.createElement('div'),
        divStyle = div.style,
        // you'll probably be better off using a `switch` instead of theses ternary ops
        support = {
            transition:
                divStyle.MozTransition     === ''? {name: 'MozTransition'   , end: 'transitionend'} :
                // Will ms add a prefix to the transitionend event?
                (divStyle.MsTransition     === ''? {name: 'MsTransition'    , end: 'msTransitionend'} :
                (divStyle.WebkitTransition === ''? {name: 'WebkitTransition', end: 'webkitTransitionEnd'} :
                (divStyle.OTransition      === ''? {name: 'OTransition'     , end: 'oTransitionEnd'} :
                (divStyle.transition       === ''? {name: 'transition'      , end: 'transitionend'} :
                false)))),
            transform:
                divStyle.MozTransform     === '' ? 'MozTransform'    :
                (divStyle.MsTransform     === '' ? 'MsTransform'     :
                (divStyle.WebkitTransform === '' ? 'WebkitTransform' : 
                (divStyle.OTransform      === '' ? 'OTransform'      :
                (divStyle.transform       === '' ? 'transform'       :
                false))))
            //, animation: ...
        };
    support.transformProp = support.transform.name.replace(/([A-Z])/g, '-$1').toLowerCase();
    return support;
}());

I have not added the code to detect “animation” properties for each browser. I’ve made this answer “community wiki” and leave that to you. :-)

David Murdoch
  • 87,823
  • 39
  • 148
  • 191
  • the cited source code has been moved to https://github.com/louisremi/jquery.transition.js/ – Tomas Ramirez Sarduy Jan 21 '13 at 09:32
  • 2
    Note: the code in the github repo has changed since this answer; the code there and the code here are completely different now. – David Murdoch Jan 21 '13 at 14:06
  • 1
    Note that if there is an CSS rule that assigns the animation to the object, setting it to `''` would make it inherit from the CSS rule, in that case it's necessary to set it to `none`. – user202729 Jan 28 '19 at 02:14
14

You have to first remove the animation, then add it again. Eg:

document.getElementById("box").style.webkitAnimationName = "";
setTimeout(function ()
{
    document.getElementById("box").style.webkitAnimationName = "shake";
}, 0);

To do this without setTimeout remove the animation during onmousedown, and add it during onclick:

someElem.onmousedown = function()
{
    document.getElementById("box").style.webkitAnimationName = "";
}
someElem.onclick = function()
{
    document.getElementById("box").style.webkitAnimationName = "shake";
}
gilly3
  • 87,962
  • 25
  • 144
  • 176
8

Following the suggestion from https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Tips, remove and then add the animation class, using requestAnimationFrame to ensure that the rendering engine processes both changes. I think this is cleaner than using setTimeout, and handles replaying an animation before the previous play has completed.

$('#shake-the-box').click(function(){   
  $('#box').removeClass("trigger");
  window.requestAnimationFrame(function(time) {
  window.requestAnimationFrame(function(time) {
      $('#box').addClass("trigger");
    });
    });    

});

http://jsfiddle.net/gcmwyr14/5/

Debby Mendez
  • 691
  • 10
  • 16
6

A simple but effective alternative:

HTML:

<div id="box"></div>
<button id="shake-the-box">Shake it!</button>​

css:

#box{
    background: blue;
    margin:30px;
    height:50px;
    width:50px;
    position:relative;
    -moz-animation:shake .2s 0 linear 1; 
    -webkit-animation:shake .2s 0 linear 1; 
}
#box.trigger{
    display:table;
}
@-webkit-keyframes shake {
    0% {
        left: 0;
    }
    25% {
        left: 12px;
    }
    50% {
        left: 0;
    }
    75% {
        left: -12px;
    }
    100% {
        left:0;
    }
}
@-moz-keyframes shake {
    0% {
        left: 0;
    }
    25% {
        left: 12px;
    }
    50% {
        left: 0;
    }
    75% {
        left: -12px;
    }
    100% {
        left:0;
    }
}​

jQuery:

$('#shake-the-box').click(function(){

  $('#box').toggleClass('trigger');

});​

Demo:
http://jsfiddle.net/5832R/2/

Issues:
I don't know if it works on Firefox, because the animation doesn't seem to work there...

Puyol
  • 3,084
  • 5
  • 25
  • 33
  • This works, but for some reason the overflow rule is ignored..I have it set to hidden but after the animation overflow shows – Snowman Oct 13 '12 at 15:20
2

Clone works pretty good on paused Karaoke: On IE11 had to force a reflow (R. Krupiński's shorter version).

$('#lyrics').text("Why does it hurt when I pee?");
changeLyrics('3s');  

function changeLyrics(sec) {
  str = 'lyrics '+ sec + ' linear 1';
  $('#lyrics').css( 'animation', str);
  $('#lyrics').css( 'animation-play-state', 'running' );
  $('#lyrics').replaceWith($('#lyrics').clone(true)); 
}

or you can use the following:

function resetAnimation(elm) {
  $('#'+elm).replaceWith($('#'+elm).clone(true)); 
}
2

Reset the value first. Use reflow to apply the change without using timeout:

function shake() {
  var box = document.getElementById("box");
  box.style.animationName = null;
  box.offsetHeight; /* trigger reflow */
  box.style.animationName = "shake";
}
@keyframes shake {
    0% { left: 0; }
   25% { left: 12px; }
   50% { left: 0; }
   75% { left: -12px; }
  100% { left: 0; }
}

#box {
    position: absolute;
    width: 75px; height: 75px;
    background-color: black;
    animation-duration: .02s;
    animation-iteration-count: 10;
    animation-timing-function: linear;
}

button {
    position: absolute;
    top: 100px;
}
<div id="box"></div>
<button onclick="shake()">Shake</button>

In contrast to the accepted answer that recommends animationEnd, this method resets the animation even when it's still in progress. This might be or might be not what you want.


An alternative would be to create a duplicate @keyframes animation and switch between the two:

function shake() {
  var box = document.getElementById("box");
  if (box.style.animationName === "shake")
      box.style.animationName = "shake2";
  else
      box.style.animationName = "shake";
}
@keyframes shake {
    0% { left: 0; }
   25% { left: 12px; }
   50% { left: 0; }
   75% { left: -12px; }
  100% { left: 0; }
}

@keyframes shake2 {
    0% { left: 0; }
   25% { left: 12px; }
   50% { left: 0; }
   75% { left: -12px; }
  100% { left: 0; }
}

#box {
    position: absolute;
    width: 75px; height: 75px;
    background-color: black;
    animation-duration: .02s;
    animation-iteration-count: 10;
    animation-timing-function: linear;
}

button {
    position: absolute;
    top: 100px;
}
<div id="box"></div>
<button onclick="shake()">Shake</button>
user
  • 23,260
  • 9
  • 113
  • 101
  • note: per https://stackoverflow.com/questions/21664940/force-browser-to-trigger-reflow-while-changing-css, it may be better to use e.g. `void( box.offsetHeight);` instead, because statement with var access and with no effect may be optimized out (and will trigger linters to emit warnings like `Unexpected expression '.' in statement position.` - courtesy of JSLint) –  Jul 18 '17 at 18:32
  • also, note that reflows are quite "heavy" (computationally intensive) operations. –  Jul 18 '17 at 18:46
1

Is there an issue with using setTimeout() to remove the class and then read it 5ms later?

svg.classList.remove('animate');
setTimeout(function() {
  svg.classList.add('animate');
}, 10);
Jeremy Lynch
  • 6,780
  • 3
  • 52
  • 63
0

1) Add animation name to the #box.trigger in css

#box.trigger{
    display:table;
    animation:shake .2s 0 linear 1;
    -moz-animation:shake .2s 0 linear 1; 
    -webkit-animation:shake .2s 0 linear 1;
}

2) In java-script you cannot remove the class trigger.

3) Remove the the class name by using setTimeOut method.

$(document).ready(function(){
    $('#shake-the-box').click(function(){

    $('#box').addClass('trigger');
    setTimeout(function(){
        $("#box").removeClass("trigger")},500)        
    });
}); 

4) Here is the DEMO.

Raein Hashemi
  • 3,346
  • 4
  • 22
  • 33
0

With your javascript, you could also add (and then remove) a CSS class in which the animation is declared. See what I mean ?

#cart p.anim {
  animation: demo 1s 1; // Fire once the "demo" animation which last 1s
}
Nico Prat
  • 686
  • 2
  • 6
  • 13