Como normalizo funções de transição CSS3 entre navegadores?

91

O evento de fim de transição do Webkit é chamado webkitTransitionEnd, Firefox é transactionEnd, opera é oTransitionEnd. Qual é uma boa maneira de lidar com todos eles em JS puro? Devo fazer uma detecção de navegador? ou implementar cada um separadamente? Alguma outra maneira que não me ocorreu?

ie:

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

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

ou

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

function fn() {
   //do whatever
}
método de ação
fonte
Para que propósito é o falso?
me ligue em

Respostas:

166

Há uma técnica usada na Modernizr, melhorada:

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

Então, você pode simplesmente chamar essa função sempre que precisar do evento de término da transição:

var transitionEnd = transitionEndEventName();
element.addEventListener(transitionEnd, theFunctionToInvoke, false);
webinista
fonte
3
oTransitionEnd foi colocado em minúsculas para otransitionend no Opera. Veja opera.com/docs/specs/presto2.10/#m274
vieron
1
também é a transição final em todas as letras minúsculas agora. Consulte dev.w3.org/csswg/css3-transitions/#transition-events
gossi
1
Removi o bit MsTransition, mas deixarei o resto da resposta intacta. As versões atuais de todos os principais navegadores não WebKit não exigem um prefixo do fornecedor. transitione transitionendsão suficientes. Veja: caniuse.com/#search=transitions
webinista
4
Por que é necessário redefinir undefined?
Atav32
1
@ Atav32, também me pergunto. A única coisa que consigo pensar é que está lá, caso alguém já tenha redefinido como algo.
Qtax
22

De acordo com o comentário de Matijs, a maneira mais fácil de detectar eventos de transição é com uma biblioteca, jquery neste 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')
});

Em javascript sem biblioteca, fica um pouco prolixo:

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

function callfunction() {
   //do whatever
}
método de ação
fonte
Aquele penúltimo não deve ser camelCased.
wwaawaw
7
engraçado, vim aqui porque meus colegas acabaram de descobrir que vários eventos foram lançados em seu código que se parecia exatamente com esta resposta
depois de
1
@Duopixel teste sua resposta e considere alterá-la, porque ela lança dois eventos no Chrome e no Safari (e pelo menos em todos os outros navegadores Webkit, além do Firefox e Opera antigos). msTransitionendnão é necessário aqui.
Dan
1
Ele irá disparar vários eventos se você tiver mais de uma propriedade transferida. Consulte: stackoverflow.com/a/18689069/740836
Nick Budden
8

Atualizar

O que segue é uma maneira mais limpa de fazer isso e não requer modernização

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

Isso é baseado no código sugerido pela Modernizr, mas com o evento extra para versões mais recentes do Opera.

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

Tom
fonte
1
Esta é uma ótima maneira de fazer isso, mas requer Modernizr. Isso pode ser escrito de forma simples, mas sem Modernizr?
alt
2
A versão jQuery dispara dois eventos em navegadores baseados em Webkit (pelo menos).
Dan
2
@Dan eu uso um em vez de ligado, então ele só vai disparar uma vez
Tom
Desculpe, não percebi que você tinha em onevez de on. Era tão óbvio!
Dan
8

Se você usar jQuery e o Bootstrap $.support.transition.endretornará o evento correto para o navegador atual.

Ele é definido no Bootstrap e usado em seus callbacks de animação , embora os documentos do jQuery digam para não confiar nessas propriedades:

Embora algumas dessas propriedades estejam documentadas a seguir, elas não estão sujeitas a um longo ciclo de depreciação / remoção e podem ser removidas quando o código jQuery interno não precisar mais delas.

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

meleyal
fonte
2
Sendo a solução mais simples aqui, é uma pena que haja essa ressalva.
Ninjakannon
1
É adicionado ao código aqui github.com/twbs/bootstrap/blob/…
Tom
6

A partir de 2015, este one-liner deve fazer o negócio (IE 10+, Chrome 1+, Safari 3.2+, FF 4+ e Opera 12 +): -

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

Anexar o ouvinte de evento é simples: -

element.addEventListener(transEndEventName , theFunctionToInvoke);
Salman von Abbas
fonte
Solução adorável. Infelizmente, ele não dirá se transitionendnão é compatível: var transEndEventName = ('WebkitTransition' in document.documentElement.style) ? 'webkitTransitionEnd' : ('transitionend' in document.documentElement.style) ? 'transitionend' : false; E então faça uma verificação simples: if(transEndEventName) element.addEventlistener(transEndEventName, theFunctionToInvoke)
Luuuud
Acho que deve ser verificado separadamente: stackoverflow.com/a/29591030/362006
Salman von Abbas
Esta resposta também se aplica a agora? (Janeiro de 2016)
Jessica
Acabei de testá-lo no IE 11 e ele retornou falso
Jessica
1

O segundo é o caminho a seguir. Apenas um desses eventos será acionado em cada navegador, portanto, você pode definir todos eles e funcionará.

Lea Verou
fonte
1

Aqui está uma maneira mais limpa

 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
fonte
0

o fechamento do Google garante que você não precise fazer isso. Se você tiver um elemento:

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

olhando para a fonte de goog.events.eventtype.js, TRANSITIONEND é calculado olhando para o 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
fonte
0

Eu uso código assim (com 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
}

Isso me permite usar JS para adicionar coisas especificando vP concatenado com a propriedade, e se não atingiu um navegador, ele apenas usa o padrão. Os eventos me permitem ligar facilmente como:

object.bind(transitionEnd,function(){
    callback();
});
Rich Bradshaw
fonte
Obrigado! Acabei fazendo algo parecido, mas sem farejar o navegador. Você pode ver o resultado (e o código) aqui: cssglue.com/cubic . O único problema com sua solução é que - se os fornecedores de navegadores decidirem padronizar seus eventos de transição - eles podem descartar seus prefixos e parar de funcionar (improvável, ainda). Mas sim, torna o código muito mais limpo.
método de ação
Concordo, tenho pretendido substituir o meu por algo melhor, mas, por outro lado, gosto da simplicidade.
Rich Bradshaw,
2
Por que vale a pena. Isso pode ser feito sem o navegador farejando, apenas fazendoobject.bind('transitionend oTransitionEnd webkitTransitionEnd', function() { // callback } );
Matijs
1
A versão sem prefixo do evento é nomeada transitionend, não TransitionEnd.
mgol
0

substituição 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);

e uso como:

$('myDiv').on('transitionend', function() { ... });
São butuv
fonte
0

A resposta aceita está correta, mas você não precisa recriar esse elemento repetidamente e ...

Crie uma variável global e adicione a (s) função (õ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));
centuriano
fonte