92

Webkit's transition end event is called webkitTransitionEnd, Firefox is transitionEnd, opera is oTransitionEnd. What is a good way of tackling all of them in pure JS? Should I do browser sniffing? or implement each one separately? Some other way that hasn't occurred to me?

i.e.:

//doing browser sniffing
var transitionend = (isSafari) ? "webkitTransitionEnd" : (isFirefox) ? "transitionEnd" : (isOpera) ? "oTransitionEnd";

element.addEventListener(transitionend, function(){
  //do whatever
},false);

or

// Assigning an event listener per browser
element.addEventListener("webkitTransitionEnd", fn);
element.addEventListener("oTransitionEnd", fn);
element.addEventListener("transitionEnd", fn);

function fn() {
   //do whatever
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
methodofaction
  • 70,885
  • 21
  • 151
  • 164

11 Answers11

169

There's a technique used in Modernizr, improved:

function transitionEndEventName () {
    var i,
        undefined,
        el = document.createElement('div'),
        transitions = {
            'transition':'transitionend',
            'OTransition':'otransitionend',  // oTransitionEnd in very old Opera
            'MozTransition':'transitionend',
            'WebkitTransition':'webkitTransitionEnd'
        };

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

    //TODO: throw 'TransitionEnd event is not supported in this browser'; 
}

Then you can just call this function whenever you need the transition end event:

var transitionEnd = transitionEndEventName();
element.addEventListener(transitionEnd, theFunctionToInvoke, false);
Dan
  • 55,715
  • 40
  • 116
  • 154
webinista
  • 3,750
  • 1
  • 20
  • 21
  • 3
    oTransitionEnd was lowercased to otransitionend in Opera. See http://www.opera.com/docs/specs/presto2.10/#m274 – vieron Aug 31 '12 at 08:28
  • 1
    it also is transitionend in all lowercase now. See http://dev.w3.org/csswg/css3-transitions/#transition-events – gossi Sep 07 '12 at 21:06
  • Yeah, to get this to work in Firefox now `'transition':'transitionEnd'` needs to be `'transition':'transitionend'` – alt Dec 19 '12 at 05:57
  • Also you can remove 'MSTransition':'msTransitionEnd' as Internet explorer 10 support standard syntax and internet explorer 9 doesn't support transitionEnd event at all. I will try to edit the answer. – redochka Jan 09 '13 at 15:20
  • For some reason this code failed in FF for me. Solved the issue thanks to this page http://marakana.com/static/bookshelf/css3_transitions_tutorial/javascript.html – Aurelio Jan 11 '13 at 02:53
  • 1
    I removed the MsTransition bit, but will leave the rest of the answer in tact. The current versions of all major non-WebKit browsers do not require a vendor prefix. `transition` and `transitionend` are enough. See: http://caniuse.com/#search=transitions – webinista Jan 19 '13 at 14:48
  • Webkit in version 534 has problem with this. For images function return transitionend but in the end webkitTransitionEnd is fired. – sebastian Jun 07 '13 at 14:04
  • It's worth mentioning that this check doesn't work in some Android 4 browsers which falsely report support for the unprefixed version. See https://github.com/Modernizr/Modernizr/issues/897 . This can easily be fixed by defining the prefixed version before the unprefixed in the `transition`-object. While objects officially don't maintain an order, WebKit browsers do. – Johannes Ewald Sep 10 '14 at 13:24
  • 4
    Why does it need to redefine `undefined`? – Atav32 Dec 12 '14 at 02:03
  • 1
    @Atav32, I wonder that too. The only thing I can think of is that it's there in case someone else redefined it to something already. – Qtax Mar 04 '15 at 15:01
22

As per Matijs comment, the easiest way to detect transition events is with a library, jquery in this case:

$("div").bind("webkitTransitionEnd.done oTransitionEnd.done otransitionend.done transitionend.done msTransitionEnd.done", function(){
  // Unlisten called events by namespace,
  // to prevent multiple event calls. (See comment)
  // By the way, .done can be anything you like ;)
  $(this).off('.done')
});

In library-less javascript it gets a bit verbose:

element.addEventListener('webkitTransitionEnd', callfunction, false);
element.addEventListener('oTransitionEnd', callfunction, false);
element.addEventListener('transitionend', callfunction, false);
element.addEventListener('msTransitionEnd', callfunction, false);

function callfunction() {
   //do whatever
}
yckart
  • 32,460
  • 9
  • 122
  • 129
methodofaction
  • 70,885
  • 21
  • 151
  • 164
  • That second-to-last one shouldn't be camelCased. – wwaawaw Nov 04 '12 at 06:02
  • 7
    funny enough, I came here 'cause my colleagues just discovered multiple events were thrown in their code which looked exactly like this answer – depoulo May 14 '13 at 13:37
  • 1
    @Duopixel please test your answer and consider changing it, because it throws two events in Chrome and Safari (and at least all other Webkit browsers plus old firefox and opera). `msTransitionend` is not needed here. – Dan May 27 '14 at 17:42
  • 1
    It will trigger multiple events if you have more than one property transitioned. See: http://stackoverflow.com/a/18689069/740836 – Nick Budden Apr 13 '15 at 11:38
8

Update

The following is a cleaner way of doing it, and doesn't require modernizr

$(".myClass").one('transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd', 
function() {
 //do something
});

Alternatively

var transEndEventNames = {
        'WebkitTransition': 'webkitTransitionEnd',
        'MozTransition': 'transitionend',
        'OTransition': 'oTransitionEnd otransitionend',
        'msTransition': 'MSTransitionEnd',
        'transition': 'transitionend'
    }, transitionEnd = transEndEventNames[Modernizr.prefixed('transition')];

This is based on the code suggested by Modernizr, but with the extra event for newer versions of Opera.

http://modernizr.com/docs/#prefixed

Tom
  • 12,591
  • 13
  • 72
  • 112
8

If you use jQuery and Bootstrap $.support.transition.end will return the right event for the current browser.

It is defined in Bootstrap and used in its animation callbacks, though the jQuery docs say not to rely on these properties:

Although some of these properties are documented below, they are not subject to a long deprecation/removal cycle and may be removed once internal jQuery code no longer needs them.

http://api.jquery.com/jQuery.support/

Qtax
  • 33,241
  • 9
  • 83
  • 121
meleyal
  • 32,252
  • 24
  • 73
  • 79
  • 2
    Being the simplest solution here, it's a real shame this has such a caveat. – Ninjakannon Aug 13 '13 at 20:14
  • 1
    It's added in their code here https://github.com/twbs/bootstrap/blob/7f0b487477bc63bef488dff14a0c28ea2106286b/js/bootstrap-transition.js – Tom Jan 30 '15 at 10:31
6

As of 2015, this one-liner should do the deal (IE 10+, Chrome 1+, Safari 3.2+, FF 4+ and Opera 12+):-

var transEndEventName = ('WebkitTransition' in document.documentElement.style) ? 'webkitTransitionEnd' : 'transitionend'

Attaching the event listener is simple:-

element.addEventListener(transEndEventName , theFunctionToInvoke);
Samuel Katz
  • 24,066
  • 8
  • 71
  • 57
  • Lovely solution. Unfortunately it won't tell you if `transitionend` isn't supported at all: `var transEndEventName = ('WebkitTransition' in document.documentElement.style) ? 'webkitTransitionEnd' : ('transitionend' in document.documentElement.style) ? 'transitionend' : false;` And then do a simple check: `if(transEndEventName) element.addEventlistener(transEndEventName, theFunctionToInvoke)` – Luuuud Apr 14 '15 at 15:55
  • I think that should be checked separately: http://stackoverflow.com/a/29591030/362006 – Samuel Katz Apr 15 '15 at 04:17
  • Does this answer apply to now as well? (Jan 2016) – Jessica Jan 21 '16 at 03:01
  • Just tested it in IE 11 and it returned false – Jessica Jan 21 '16 at 04:01
1

Here is a more cleaner way

 function transitionEvent() {
      // Create a fake element
      var el = document.createElement("div");

      if(el.style.OTransition) return "oTransitionEnd";
      if(el.style.WebkitTransition) return "webkitTransitionEnd";
      return "transitionend";
    }
Nicholas
  • 3,529
  • 2
  • 23
  • 31
1

The second is the way to go. Only one of those events will fire in every browser, so you can set all of them and it'll work.

Lea Verou
  • 23,618
  • 9
  • 46
  • 48
0

google closure makes sure you don't have to do this. If you have an element:

goog.events.listen(element, goog.events.EventType.TRANSITIONEND, function(event) {
  // ... your code here
});

looking at the source of goog.events.eventtype.js, TRANSITIONEND is calculated by looking at the useragent:

// CSS transition events. Based on the browser support described at:
  // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
  TRANSITIONEND: goog.userAgent.WEBKIT ? 'webkitTransitionEnd' :
      (goog.userAgent.OPERA ? 'oTransitionEnd' : 'transitionend'),
Joe Heyming
  • 777
  • 6
  • 11
0

jquery override:

(function ($) {
  var oldOn = $.fn.on;

  $.fn.on = function (types, selector, data, fn, /*INTERNAL*/ one) {
    if (types === 'transitionend') {
      types = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd';
    }

    return oldOn.call(this, types, selector, data, fn, one);
  };
})(jQuery);

and usage like:

$('myDiv').on('transitionend', function() { ... });
Are Butuv
  • 292
  • 3
  • 10
0

I use code like this (with jQuery)

var vP = "";
var transitionEnd = "transitionend";
if ($.browser.webkit) {
    vP = "-webkit-";
    transitionEnd = "webkitTransitionEnd";
} else if ($.browser.msie) {
    vP = "-ms-";
} else if ($.browser.mozilla) {
    vP = "-moz-";
} else if ($.browser.opera) {
    vP = "-o-";
    transitionEnd = "otransitionend"; //oTransitionEnd for very old Opera
}

That lets me use JS to add things by specifying vP concatentated with the property, and if it didn't hit a browser it just uses the standard. The events lets me easily bind like so:

object.bind(transitionEnd,function(){
    callback();
});
Dan
  • 55,715
  • 40
  • 116
  • 154
Rich Bradshaw
  • 71,795
  • 44
  • 182
  • 241
  • Thanks! I ended up doing something similar, but without browser sniffing. You can see the result (and code) here: http://cssglue.com/cubic. The only problem with your solution is that—if browser vendors decide to standarize their transition events—they might drop their prefixes and they would stop working (unlikely, yet). But yes, it makes the code much more cleaner. – methodofaction Mar 31 '11 at 19:25
  • I agree, I've been meaning to replace mine with something better, but on the other hand I like the simplicity of it. – Rich Bradshaw Apr 01 '11 at 19:55
  • 2
    For what it's worth. This can be done without browser sniffing by just doing `object.bind('transitionend oTransitionEnd webkitTransitionEnd', function() { // callback } );` – Matijs Apr 20 '11 at 12:22
  • 1
    The non-prefixed version of the event is named `transitionend`, not `TransitionEnd`. – mgol Jan 17 '13 at 18:23
0

Accepted answer is correct but you don't have to re-create that element again-and-again-and...

Build a global variable and add the function(s):

(function(myLib, $, window, document, undefined){

/**
 * @summary
 * Returns the browser's supported animation end event type.
 * @desc
 * @see {@link https://jonsuh.com/blog/detect-the-end-of-css-animations-and-transitions-with-javascript/}
 * @function myLib.getAnimationEndType
 * @return {string} The animation end event type
 */
(function(){
   var type;

   myLib.getAnimationEndType = function(){
      if(!type)
         type = callback();
      return type;

      function callback(){
         var t,
             el = document.createElement("fakeelement");

         var animations = {
            "animation"      : "animationend",
            "OAnimation"     : "oAnimationEnd",
            "MozAnimation"   : "animationend",
            "WebkitAnimation": "webkitAnimationEnd"
         }

         for (t in animations){
            if (el.style[t] !== undefined){
               return animations[t];
            }
         }
      }
   }
}());

/**
 * @summary
 * Returns the browser's supported transition end event type.
 * @desc
 * @see {@link https://jonsuh.com/blog/detect-the-end-of-css-animations-and-transitions-with-javascript/}
 * @function myLib.getTransitionEndType
 * @return {string} The transition end event type
 */
(function(){
   var type;

   myLib.getTransitionEndType = function(){
      if(!type)
         type = callback();
      return type;

      function callback(){
         var t,
             el = document.createElement("fakeelement");

         var transitions = {
            "transition"      : "transitionend",
            "OTransition"     : "oTransitionEnd",
            "MozTransition"   : "transitionend",
            "WebkitTransition": "webkitTransitionEnd"
         }

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

}(window.myLib = window.myLib || {}, jQuery, window, document));
centurian
  • 1,168
  • 13
  • 25