Search code examples
javascriptjquerycallbackcss-transitionstransitionend

Enabling vendor prefixes in CSS transitions make callback fires twice


I'm implementing an excellent solution (found here) to use a callback function a la jQuery when using CSS transitions.

The problem is that if I use vendor prefixes, Chrome at least binds two events: one for webkitTransitionEnd and the second one for transitionend and, of course, fires the callback twice. Here's my piece of code:

jQuery("#main").one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
  console.log("POUM!");
});

Am I doing something wrong?


Solution

  • You're not doing anything wrong. Chrome just uses both the prefixed and un-prefixed versions.

    There are a couple options:

    1. Using an outside variable.

      var fired = false;
      jQuery("#main").one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
          if ( ! fired ) {
              fired = true;
              console.log("POUM!");
          }
      });
      
    2. Using some kind of detection to get a single variable for transitionend (the below uses Modernizr, and is taken from their documentation):

      var transitionend = (function(transition) {
           var transEndEventNames = {
               'WebkitTransition' : 'webkitTransitionEnd',// Saf 6, Android Browser
               'MozTransition'    : 'transitionend',      // only for FF < 15
               'transition'       : 'transitionend'       // IE10, Opera, Chrome, FF 15+, Saf 7+
          };
      
          return transEndEventNames[transition];
      })(Modernizr.prefixed('transition'));
      
      // then
      jQuery("#main").one(transitionend, function(e) {
          console.log("POUM!");
      });
      

    NOTE:

    Safari 6 seems to trigger onload for anything that is set in the CSS. So, if you have (assuming all prefixes)

    #main {
        width: 40px;
        height: 40px;
        transition: all 200ms;
    }
    

    Safari will trigger the transitionend with width and height on load. There are a couple ways to get around this:

    • Use more specific transition-property (but if you set that in the CSS, it will still trigger)
    • Do the following in javascript (it's not the prettiest thing, but it should take care of that edge case and it still works in Chrome) fiddle

      var transitionProperty = 'background-color',
          startColor = jQuery("#main").on(transitionend, function(e) {
              var el = $(this);
              if ( transitionProperty === e.originalEvent.propertyName && el.css(transitionProperty) !== startColor ) {
                  console.log("POUM!");
                  // This is to make it only happen once.
                  $(this).off(transitionend);
              }
          }).css(transitionProperty);