如何规范跨浏览器的 CSS3转换结束事件?

Webkit 的转换结束事件被称为 webkitTrantionEnd,Firefox 是 trantionEnd,Opera 是 oTrantionEnd。在纯 JS 中处理所有这些问题的好方法是什么?我应该做浏览器嗅探吗?还是分别执行?还有其他我没想到的办法吗?

例如:

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


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

或者

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


function fn() {
//do whatever
}
39252 次浏览

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.

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

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
}

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

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

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/

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'),

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

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() { ... });

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

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