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

JavascriptJqueryCssCss Transitions

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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);
        });

Solution 6 - Javascript

Chainable one-way events with promises

In case that you need one-way events just like JQuery's one(), I found this pattern handy:

function awaitTransitionEnd(transitionProperty, el, triggerFunction) {
    return new Promise((resolve, reject) => {
        const handler = (e) => {
            if (e.propertyName !== transitionProperty) {
                return;
            }
            el.removeEventListener('transitionend', handler);
            resolve(e);
        }
        el.addEventListener('transitionend', handler);
        triggerFunction(el);
    });
}

You can then chain CSS transitions like in this example:

awaitTransitionEnd(
    'background-color', myEl, () => myEl.classList.replace('bg-red', 'bg-green')
).then(() => awaitTransitionEnd(
    'opacity', myEl, () => myEl.classList.add('opacity-0')
)).then(() => awaitTransitionEnd(
    'opacity', myEl, () => myEl.classList.remove('opacity-0')
));

If you don't want to use arrow functions, you must pass the event + element like so:

awaitTransitionEnd('background-color', myEl, function(el) {
    el.classList.replace('bg-red', 'bg-green');
}).then(function(e) {
    return awaitTransitionEnd('opacity', e.target, function(el) {
        el.classList.add('opacity-0');
    });  
}).then(function(e) {
    return awaitTransitionEnd('opacity', e.target, function(el) {
        el.classList.remove('opacity-0');
    });
});

When awaitTransitionEnd is a class method and you don't want to use arrow functions, you must bind this to each then()-closure:

//[...]
.then(function(e) {
    return this.awaitTransitionEnd('opacity', e.target, function(el) {
        el.classList.add('opacity-0');
    });  
}.bind(this)).then(//[...]

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionC.J.View Question on Stackoverflow
Solution 1 - JavascriptJim JeffersView Answer on Stackoverflow
Solution 2 - JavascriptYehuda SchwartzView Answer on Stackoverflow
Solution 3 - JavascriptGaffView Answer on Stackoverflow
Solution 4 - JavascriptDavid SinclairView Answer on Stackoverflow
Solution 5 - JavascriptChuck Le ButtView Answer on Stackoverflow
Solution 6 - JavascriptBenniView Answer on Stackoverflow