How to use jQuery to wait for the end of CSS3 transitions?

143,037

Solution 1

For transitions you can use the following to detect the end of a transition via jQuery:

$("#someSelector").bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){ ... });

Mozilla has an excellent reference:

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions#Detecting_the_start_and_completion_of_a_transition

For animations it's very similar:

$("#someSelector").bind("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", function(){ ... });

Note that you can pass all of the browser prefixed event strings into the bind() method simultaneously to support the event firing on all browsers that support it.

Update:

Per the comment left by Duck: you use jQuery's .one() method to ensure the handler only fires once. For example:

$("#someSelector").one("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(){ ... });

$("#someSelector").one("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", function(){ ... });

Update 2:

jQuery bind() method has been deprecated, and on() method is preferred as of jQuery 1.7. bind()

You can also use off() method on the callback function to ensure it will be fired only once. Here is an example which is equivalent to using one() method:

$("#someSelector")
.on("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd",
 function(e){
    // do something here
    $(this).off(e);
 });

References:

Solution 2

There is an animationend Event that can be observed see documentation here, also for css transition animations you could use the transitionend event

There is no need for additional libraries these all work with vanilla JS

document.getElementById("myDIV").addEventListener("transitionend", myEndFunction);
function myEndFunction() {
	this.innerHTML = "transition event ended";
}
#myDIV {transition: top 2s; position: relative; top: 0;}
div {background: #ede;cursor: pointer;padding: 20px;}
<div id="myDIV" onclick="this.style.top = '55px';">Click me to start animation.</div>

Solution 3

Another option would be to use the jQuery Transit Framework to handle your CSS3 transitions. The transitions/effects perform well on mobile devices and you don't have to add a single line of messy CSS3 transitions in your CSS file in order to do the animation effects.

Here is an example that will transition an element's opacity to 0 when you click on it and will be removed once the transition is complete:

$("#element").click( function () {
    $('#element').transition({ opacity: 0 }, function () { $(this).remove(); });
});

JS Fiddle Demo

Solution 4

For anyone that this might be handy for, here is a jQuery dependent function I had success with for applying a CSS animation via a CSS class, then getting a callback from afterwards. It may not work perfectly since I had it being used in a Backbone.js App, but maybe useful.

var cssAnimate = function(cssClass, callback) {
    var self = this;

    // Checks if correct animation has ended
    var setAnimationListener = function() {
        self.one(
            "webkitAnimationEnd oanimationend msAnimationEnd animationend",
            function(e) {
                if(
                    e.originalEvent.animationName == cssClass &&
                    e.target === e.currentTarget
                ) {
                    callback();
                } else {
                    setAnimationListener();
                }
            }
        );
    }

    self.addClass(cssClass);
    setAnimationListener();
}

I used it kinda like this

cssAnimate.call($("#something"), "fadeIn", function() {
    console.log("Animation is complete");
    // Remove animation class name?
});

Original idea from http://mikefowler.me/2013/11/18/page-transitions-in-backbone/

And this seems handy: http://api.jqueryui.com/addClass/


Update

After struggling with the above code and other options, I would suggest being very cautious with any listening for CSS animation ends. With multiple animations going on, this can get messy very fast for event listening. I would strongly suggest an animation library like GSAP for every animation, even the small ones.

Solution 5

The accepted answer currently fires twice for animations in Chrome. Presumably this is because it recognizes webkitAnimationEnd as well as animationEnd. The following will definitely only fires once:

/* From Modernizr */
function whichTransitionEvent(){

    var el = document.createElement('fakeelement');
    var transitions = {
        'animation':'animationend',
        'OAnimation':'oAnimationEnd',
        'MSAnimation':'MSAnimationEnd',
        'WebkitAnimation':'webkitAnimationEnd'
    };

    for(var t in transitions){
        if( transitions.hasOwnProperty(t) && el.style[t] !== undefined ){
            return transitions[t];
        }
    }
}

$("#elementToListenTo")
    .on(whichTransitionEvent(),
        function(e){
            console.log('Transition complete!  This is the callback!');
            $(this).off(e);
        });
Share:
143,037

Related videos on Youtube

C.J.
Author by

C.J.

Updated on January 07, 2022

Comments

  • C.J.
    C.J. over 2 years

    I'd like to fade out an element (transitioning its opacity to 0) and then when finished remove the element from the DOM.

    In jQuery this is straight forward since you can specify the "Remove" to happen after an animation completes. But if I wish to animate using CSS3 transitions is there anyway to know when the transition/animation has completed?

  • Jonathan Liuti
    Jonathan Liuti over 10 years
    The callback is not working - maybe it's due to a change in transit's api I don't know, but the fiddle example is not working. It triggers the hide method before the animation runs (tried in chrome)
  • Gaff
    Gaff over 10 years
    @JonathanLiuti tested using FireFox 25, IE11, Chrome 31. Works fine.
  • Jonathan Liuti
    Jonathan Liuti over 10 years
    Yea @ROFLwTIME you are totally right - it seems my chrome was just going crazy. Retested the fiddle today after a clean restart of chrome and it's working as expected. My bad ! Sorry 'bout this one.
  • SikoSoft
    SikoSoft over 10 years
    It's worth noting that the callback will be fired for every child element that transits as well. This is very important to keep in mind in case you are wondering why your callback gets fired more times than you were expecting. I am not aware of any work-around as of now.
  • Jim Jeffers
    Jim Jeffers over 10 years
    @Lev you could easily mitigate this by comparing the event's currentTarget to its target. So something like: function(event){ if(event.target === event.currentTarget){ /* Do stuff */ } }
  • SikoSoft
    SikoSoft over 10 years
    Yeah, I figured that out shortly after I wrote the comment. >_< Thanks for posting it though; I'm sure it will help others! :)
  • olo
    olo almost 10 years
    We use .on() rather than .bind() for jQuery v1.7+ api.jquery.com/bind
  • kroe
    kroe almost 10 years
    @JimJeffers you can use the "return home early" pattern, by doing, if( event.target != event.currentTarget ) return;
  • ditto
    ditto over 9 years
    Use .one() instead of .on() or .bind(). This will ensure that it may only run once, and afterwards it'll unbind itself from the event handler.
  • DMSJax
    DMSJax over 8 years
    Because I ran into a similar problem, i found the answer elsewhere however this SO answer was in my open tabs (just found answer on another tab first) anyways - here is a simple jsfiddle example of @AJFarmer use of .one() on a CSS animation transition Jsfiddle
  • WebDevNerdStuff
    WebDevNerdStuff over 8 years
    All modern browsers now support the unprefixed event. caniuse.com/#feat=css-transitions Also note that if you have "transitionend webkitTransitionEnd" it will be triggered twice in Chrome.
  • Xharlie
    Xharlie about 8 years
    I prefer one() because you don't have to construct another jQuery object with $(this) just to remove the binding in the event handler. It just seems cleaner.
  • Xharlie
    Xharlie about 8 years
    Since jQuery is in the picture, anyway, I have a simple and robust solution to ensure that a callback only gets fired once: the code that begins the transition returns a promise that is resolved by the animationend handler. The code that is to run after the animation ends is simply queued on the promise with then()
  • Jason Kleban
    Jason Kleban almost 8 years
    Here's an example fiddle of this technique that demonstrates animating the attach/removal of an element without any timeouts. It is in typescript can be converted to javascript by pasting into the typescript playground. No jQuery either.
  • Chuck Le Butt
    Chuck Le Butt almost 8 years
    This still fires twice in Chrome. Presumably because it supports webkitAnimationEnd as well as animationEnd.
  • Jakob Løkke Madsen
    Jakob Løkke Madsen over 7 years
    I would suggest to call the function whichAnimationEvent() instead, since it deals with animation events.
  • logidelic
    logidelic over 7 years
    This is great, but I wonder if there is a way to trigger this event immediately if there is no transition in progress. Imagine the case where some transition:xxx gets removed from CSS for aesthetic reasons, and then it inexplicably breaks code that was expecting this transitionend event upon changing of some style's value...
  • BomAle
    BomAle almost 6 years
    Thanks for share it, I have used it and edited adding e.stopImmediatePropagation(); self.trigger(this.whichAnimationEvent()); //for purge existing event callback.apply(self);
  • Blue
    Blue over 5 years
    This is a borderline link-only answer. You should expand your answer to include as much information here, and use the link only for reference.
  • Aaron Mason
    Aaron Mason over 5 years
    Voting this one up as it's the first one that doesn't rely on jQuery. No sense cutting down an entire tree for a branch.
  • A Haworth
    A Haworth over 3 years
    Now browsers are more in step - for example, not needing prefixes - this vanilla Javascript solution would probably be the accepted answer nowadays (2020). Upvoted for not relying on jQuery to answer a relatively straightforward question.