2

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?

alex
  • 479,566
  • 201
  • 878
  • 984

3 Answers3

5

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);
    
kalley
  • 18,072
  • 2
  • 39
  • 36
  • Ok, although this "issue" still makes me want to kidnap and murder some -webkit-kids, this solution is great. I even learned that Modernizr has the prefixed method! Thanks a lot, man! –  Aug 07 '13 at 15:15
  • @kalley Thanks for the script! I started using it and it worked great, until I noticed that it doesn't fire as it should in Safari anymore. Ideas? – INT Dec 19 '13 at 11:57
  • 2
    @INT It appears Safari has done what Chrome has done and removed the prefix from the property, but changed the event name to `transitionend` (no camelcase). I'm updating the script based on [Modernizr's documentation](http://modernizr.com/docs/#prefixed) – kalley Dec 19 '13 at 13:26
  • I'm still on Safari 6 (and iOS6), and it's not working as expected. Hm. – INT Dec 19 '13 at 13:51
  • 1
    @INT a coworker and I just tested this in Safari 6.1 and it was working. It might depend on your CSS. We were noticing that it was firing on whatever was in the CSS file on load (I'm not sure why), so the `one` was not firing after that. – kalley Dec 19 '13 at 15:30
  • 1
    @INT I just updated with a workaround and some additional information. Not nearly as clean as before, but it should work. – kalley Dec 19 '13 at 15:55
2

I had the same problem, with Chrome firing twice, once for "transitionend" and again for "webkitTransitionEnd". With inspiration from remyabel's solution, I ended up with something fairly simple.

jQuery("#main").one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(e) {
$(this).off("webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd    transitionend");
  console.log("POUM!");
});
KyleGill
  • 171
  • 1
  • 3
0

Not sure if this addresses your issues, but from this link: http://ianlunn.co.uk/articles/opera-12-otransitionend-bugs-and-workarounds/

It quote says:

Yup, six! In Opera 11, the transitionEnd event fired twice for every one transition ending. In Opera 12, a transitionEnd event will fire six times whether binding via JavaScript or jQuery.

This is for Opera, but I'm assuming that the same problem applies to you. It then goes to say that you can alleviate this problem by doing:

$(document).bind("otransitionend", function(){
    $(this).unbind("otransitionend");
    alert("Transition Ended");
});
  • It looks like it works, but... my god... In Opera is even worse, because it triggers otransitionend, oTransitionEnd and transitionend. Thanks anyway! –  Aug 07 '13 at 15:00