¿Cómo normalizo las funciones de transición CSS3 entre navegadores?

91

El evento final de transición de Webkit se llama webkitTransitionEnd, Firefox es transitionEnd, opera es oTransitionEnd. ¿Cuál es una buena forma de abordarlos todos en JS puro? ¿Debería rastrear el navegador? o implementar cada uno por separado? ¿De alguna otra forma que no se me haya ocurrido?

es decir:

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

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

o

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

function fn() {
   //do whatever
}
metodofaccion
fuente
¿Para qué es lo falso?
llámame

Respuestas:

166

Hay una técnica utilizada en Modernizr, mejorada:

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

Entonces puede llamar a esta función siempre que necesite el evento de finalización de la transición:

var transitionEnd = transitionEndEventName();
element.addEventListener(transitionEnd, theFunctionToInvoke, false);
webinista
fuente
3
oTransitionEnd se puso en minúscula a otransitionend en Opera. Ver opera.com/docs/specs/presto2.10/#m274
visto el
1
ahora también es el final de transición en minúsculas. Ver dev.w3.org/csswg/css3-transitions/#transition-events
gossi
1
Eliminé el bit MsTransition, pero dejaré el resto de la respuesta intacto. Las versiones actuales de los principales navegadores que no son de WebKit no requieren un prefijo de proveedor. transitiony transitionendson suficientes. Ver: caniuse.com/#search=transitions
webinista
4
¿Por qué necesita redefinirse undefined?
Atav32
1
@ Atav32, yo también me pregunto eso. Lo único en lo que puedo pensar es que está ahí en caso de que alguien más lo haya redefinido ya.
Qtax
22

Según el comentario de Matijs, la forma más fácil de detectar eventos de transición es con una biblioteca, jquery en este caso:

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

En javascript sin biblioteca, se vuelve un poco detallado:

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

function callfunction() {
   //do whatever
}
metodofaccion
fuente
Ese penúltimo no debería estar basado en camellos.
wwaawaw
7
lo suficientemente gracioso, vine aquí porque mis colegas acaban de descubrir que se
incluyeron
1
@Duopixel, por favor, pruebe su respuesta y considere cambiarla, porque arroja dos eventos en Chrome y Safari (y al menos en todos los demás navegadores Webkit más el antiguo Firefox y Opera). msTransitionendno es necesario aquí.
Dan
1
Activará varios eventos si tiene más de una propiedad en transición. Ver: stackoverflow.com/a/18689069/740836
Nick Budden
8

Actualizar

La siguiente es una forma más limpia de hacerlo y no requiere modernización

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

Alternativamente

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

Esto se basa en el código sugerido por Modernizr, pero con el evento adicional para las versiones más nuevas de Opera.

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

Tom
fuente
1
Esta es una excelente manera de hacerlo, pero requiere Modernizr. ¿Se puede escribir esto de forma sencilla pero sin Modernizr?
alt
2
La versión de jQuery dispara dos eventos en los navegadores basados ​​en Webkit (al menos).
Dan
2
@Dan Uso uno en lugar de encendido, por lo que solo se disparará una vez
Tom
Lo siento, no me di cuenta de que tienes en onelugar de on. ¡Era tan obvio!
Dan
8

Si usa jQuery y Bootstrap $.support.transition.enddevolverá el evento correcto para el navegador actual.

Se define en Bootstrap y se usa en sus devoluciones de llamada de animación , aunque los documentos de jQuery dicen que no se debe confiar en estas propiedades:

Aunque algunas de estas propiedades se documentan a continuación, no están sujetas a un ciclo prolongado de desaprobación / eliminación y pueden eliminarse una vez que el código jQuery interno ya no las necesite.

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

leal
fuente
2
Siendo la solución más simple aquí, es una verdadera lástima que tenga tal salvedad.
Ninjakannon
1
Se agrega en su código aquí github.com/twbs/bootstrap/blob/…
Tom
6

A partir de 2015, este one-liner debería hacer el trato (IE 10+, Chrome 1+, Safari 3.2+, FF 4+ y Opera 12 +): -

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

Adjuntar el detector de eventos es simple: -

element.addEventListener(transEndEventName , theFunctionToInvoke);
Salman von Abbas
fuente
Encantadora solución. Desafortunadamente, no le dirá si transitionendno es compatible en absoluto: var transEndEventName = ('WebkitTransition' in document.documentElement.style) ? 'webkitTransitionEnd' : ('transitionend' in document.documentElement.style) ? 'transitionend' : false; Y luego haga una verificación simple: if(transEndEventName) element.addEventlistener(transEndEventName, theFunctionToInvoke)
Luuuud
Creo que debería comprobarse por separado: stackoverflow.com/a/29591030/362006
Salman von Abbas
¿Esta respuesta también se aplica ahora? (Enero de 2016)
Jessica
Acabo de probarlo en IE 11 y devolvió falso
Jessica
1

El segundo es el camino a seguir. Solo uno de esos eventos se activará en cada navegador, por lo que puede configurarlos todos y funcionará.

Lea Verou
fuente
1

Aquí hay una forma más limpia

 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";
    }
Nicolás
fuente
0

El cierre de Google se asegura de que no tengas que hacer esto. Si tienes un elemento:

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

mirando la fuente de goog.events.eventtype.js, TRANSITIONEND se calcula mirando el 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
fuente
0

Yo uso un código como este (con 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
}

Eso me permite usar JS para agregar cosas especificando vP concatentado con la propiedad, y si no llegó a un navegador, solo usa el estándar. Los eventos me permiten unirme fácilmente así:

object.bind(transitionEnd,function(){
    callback();
});
Rich Bradshaw
fuente
¡Gracias! Terminé haciendo algo similar, pero sin rastrear el navegador. Puede ver el resultado (y el código) aquí: cssglue.com/cubic . El único problema con su solución es que, si los proveedores de navegadores deciden estandarizar sus eventos de transición, podrían eliminar sus prefijos y dejar de funcionar (poco probable, todavía). Pero sí, hace que el código sea mucho más limpio.
Metodofacción
Estoy de acuerdo, tenía la intención de reemplazar el mío con algo mejor, pero por otro lado, me gusta la simplicidad.
Rich Bradshaw
2
Por lo que vale. Esto se puede hacer sin tener que object.bind('transitionend oTransitionEnd webkitTransitionEnd', function() { // callback } );
rastrear el
1
La versión sin prefijo del evento se nombra transitionend, no TransitionEnd.
mgol
0

anulación de jquery:

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

y uso como:

$('myDiv').on('transitionend', function() { ... });
Son Butuv
fuente
0

La respuesta aceptada es correcta, pero no tiene que volver a crear ese elemento una y otra vez y ...

Cree una variable global y agregue la (s) función (es):

(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
fuente