48

I have the following code and my problem is that the transitionend event is fired twice. I don't know what's causing this. I suspected the vendor prefixes caused it, but they don't. Even if I only leave transitionend and transition it will still fire twice.

CSS

transition: 1s ease-out;

JS

document.addEventListener('click', function (e) {
    var submarine = document.querySelector('.submarine');
    var submarineX = e.clientX - submarine.offsetWidth / 2;
    var submarineY = e.clientY - submarine.offsetHeight / 2;

    submarine.style.left = submarineX + "px";
    submarine.style.top = submarineY + "px";
});

document.addEventListener('transitionend', function (event) {
    console.log(event.type + " " + new Date().getTime());
});

Fiddle

document.addEventListener('transitionend', function (event) {
    console.log(event.type + " " + new Date().getTime());
});

document.addEventListener('click', function (e) {
    var submarine = document.querySelector('.submarine');
    var submarineX = e.clientX - submarine.offsetWidth / 2;
    var submarineY = e.clientY - submarine.offsetHeight / 2;

    submarine.style.left = submarineX + "px";
    submarine.style.top = submarineY + "px";
});
.submarine {
    position: absolute;
    top: 0;
    left: 0;
    width: 20px;
    height: 20px;
    background-color: red;
    border-radius: 50%;
    transition: 1s ease-out;
}
<div class="submarine"></div>
Jonathan
  • 8,771
  • 4
  • 41
  • 78

6 Answers6

102

transitionend fires for each property transitioned, in your case top and left.

You can access the property associated with the event at event.propertyName.

There's no "transitionsend" event, so you will probably need some hackiness such as filtering the transitionend callback handling for only one of the transitioned properties. E.g.:

function (event) {
    if (event.propertyName == 'top') {
        //put your code here
    }
});

ps. No browser fires the MSTransitionEnd event. It was at some point in the MS docs, but sometime before the IE10 beta release it was replaced by the standard transitionend event.

Chad Killingsworth
  • 14,360
  • 2
  • 34
  • 57
Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166
  • And that's a real bummer when you have a sequence of transitions and you want to know when all are done without hard coding transition properties. – Stoutie Sep 13 '14 at 22:05
  • 1
    @stoutie if you don't mind moving all the transition styles and event handling (e.g. `:hover`) to JavaScript, you can use the [Web Animations API](http://updates.html5rocks.com/2014/05/Web-Animations---element-animate-is-now-in-Chrome-36) which gives you a clean `onfinish` callback. – Fabrício Matté Sep 14 '14 at 03:02
  • 1
    Not sure if I'm missing something here but I believe that to check the property name you need `event.propertyName` for me at least `event.originalEvent` is undefined in Chrome 38. The MDN docs seem to back me up on this: https://developer.mozilla.org/en-US/docs/Web/Events/transitionend – rich97 Oct 16 '14 at 10:37
  • 1
    @rich97 in my example code, `event` is a [jQuery event object](http://api.jquery.com/category/events/event-object/). `jQueryEvent.originalEvent` exposes the underlying [native event object](https://developer.mozilla.org/en/docs/Web/API/Event). In case you're using the native API, you already have direct access to the native event. `=]` – Fabrício Matté Oct 16 '14 at 21:28
9

The event fires for each property that has been transitioned.

The propertyName way that Fabricio suggested is the proper way to do this, however if you are using jQuery you can also use one(); as well, like this.

$(document).one('transitionend webkitTransitionEnd MSTransitionEnd', function() {
   ...
});
DisgruntledGoat
  • 70,219
  • 68
  • 205
  • 290
iConnor
  • 19,997
  • 14
  • 62
  • 97
  • +1 that works too, though that way you will have to attach the callback every time you ensue a transition. – Fabrício Matté Sep 08 '13 at 22:15
  • 1
    @FabrícioMatté Yeah, I know that's why I said *depending on the circumstances*. So whatever floats your boat. – iConnor Sep 08 '13 at 22:39
  • Also important to note if you have a sequence of transitions this will only fire for the first property to complete transitioning. – Stoutie Sep 13 '14 at 22:10
  • 1
    I use this but `transitionend webkitTransitionEnd` fire twice so I add this inside the `.one` `$(this).off('webkitTransitionEnd moztransitionend transitionend oTransitionEnd'); ` – benoît Jan 05 '15 at 16:26
  • 1
    This actually does not solve the issue at all... The callback function still gets fired for each property that is transitioned – Bill Nov 27 '15 at 17:53
  • 1
    Please do not submit jQuery solutions when it is clear that the asker is not using it... – eltiare May 19 '17 at 22:13
6

For anyone looking for a simple, one time copy and paste solution (I've only included the necessary css). This doesn't answer the question and it does answer what I was looking for when I landed here.

CSS:

.my-elem {
    transition: height 0.5s ease-out, opacity 0.5s ease-out;
}

JavaScript:

var elem = document.querySelector(".my-elem");

var transitionCounter = 0;

var transitionProp = window.getComputedStyle(elem , null)["transition-property"] || "";

// We just need to know how many transitions there are
var numTransitionProps = transitionProp.split(",").length;

elem.addEventListener("transitionend", (event) => {
  // You could read event.propertyName to find out which transition was ended, 
  // but it's not necessary if you just want to know when they are all done.
  if (transitionCounter < (numTransitionProps - 1)) {
    transitionCounter++;
  } else {
    transitionCounter = 0; // reset
    alert("I'm done!!!"); // do what you need to
  }
}, false);

Tested in IE11, Chrome 48 and Firefox 37.

T. Junghans
  • 11,385
  • 7
  • 52
  • 75
4

For anyone still looking for a more robust solution, like "allTransitionEnd" event, I've implemented a jQuery "special event", more as a proof of concept for something I was working on, but I might put out a lib on Github.

Check out the JSBin.

It's quite tricky, so I won't explain too much, but it makes it real easy to do stuff after ALL transitions have ended on an element:

$(function () {

    $element.on('allTransitionEnd', function () {
        // do something after all transitions end.
    });

});

It works by probing the element for transition properties, then binds to the native transitionend events (vendor specific included) in order to keep track of properties that have finished transitioning. When all have finished transitioning it triggers any bound allTransitionsEnd handlers and then clears transition properties, in case they've changed as well, and probes for them fresh next time around.

This is really useful when multiple properties are being transitioned with varying delay and/or duration and you want to do something after all transitions have completed.

Example use cases:

  • Remove a flash message after fade-out and shrink.
  • Triggering "opened" and "closed" events in a reusable component, such as a menu or modal, where consumers may want to execute some logic after the transition has ended, without prying into css transitions.

If you are only transitioning one property, or have no varied delays and/or durations, then a simple solution works fine.

Works in latest version of Chrome, Firefox, Safari, Mobile Safari and IE11 and IE10. Doesn't work in IE8 because transitions are not supported. Bind to an additional native event as fallback.

Stoutie
  • 1,944
  • 1
  • 21
  • 17
0

You can use the target property to filter out events that are triggered by child elements and use propertyName to only trigger the event when a specific property changed.

const handleTransitionEnd = event => {
  if (event.target !== myDomElementRef) return;
  if (event.propertyName !== "height") return;

  // Do my things
};
Etienne Martin
  • 10,018
  • 3
  • 35
  • 47
-3

This is a relatively old question but I thought I'd share my answer:

function OnTransitionEvent() {
    var t,
        el = document.createElement('transitionElement');

    var transitions = {
        'transition'      : 'transitionend',
        'OTransition'     : 'oTransitionEnd',
        'MozTransition'   : 'transitionend',
        'WebkitTransition': 'webkitTransitionEnd'
    };

    for (t in transitions){
        if (el.style[t] !== undefined){
            return transitions[t];
        }
    }
}

var transitionEvent = OnTransitionEvent();

$(document).one(transitionEvent, function() { 
    console.log('done');
});
Howard
  • 3,648
  • 13
  • 58
  • 86