transitionEnd event with multiple transitions, detect last transition

I now it's maybe old question, but encounter same trouble. Solved in that way:

document.querySelector('a').addEventListener('click', function(e){
  this.classList.toggle('animate');
  let style = window.getComputedStyle(this, null);
  Promise.all(style.transition.split(',').map((prop) => {
     prop = prop.trim().split(/\s+/);
     return Promise.race([
         new Promise((resolve) => {
              let h = (e) => {
                   if (e.propertyName == prop[0])
                        resolve('transitionEnd ' + prop[0]);
              };
              this.addEventListener('transitionend', h, {once:false});
         }),
         new Promise((resolve) => 
              setTimeout(
                  () => resolve('TIMEOUT ' + prop[0]),
                  prop[1].replace(/s/,'')*1000 + 100
              )
         )
     ])
  }))
  .then((res) => {
     console.log(res + 'DONE!!!!');
     /*  do your stuff */
  });
});

Explanation:

  • first we determine what properties actually should be changed, then split/map/convert it to perform manipulation. usually it have format like: "width 4s ease 0s, height 2s ease 0s,.."
  • next we use Promise.race, since not always transitionEnd will be triggerred (either prop is the same for the both states, or prop name will not match in rules and event (background may be background-color), and other) we need tp fallback to simple timeout
  • finally we wait for all timeout/eventListeners for each property

Disclaimer:

  • it's example I admit it use a lot of magic, and missed lot of checks
  • "callback" will be fired even in case if animation was cancelled (timeout will fire anyway)
  • requires ES6 :)

you can play around with it on jsfiddle


A bit of a hacky solution might be to try to find out which css property has the longest total duration. You can do so by using window.getComputedStyle on your <a> element and adding up all duration and delay properties.

You could do this in the regular event handler that is fired three times (it's pretty quick), or make a function that pre-computes the property name you're looking for.

Main problems with this approach:

  • Css allows you to use ms and s in one statement, you might need to do some more computing.
  • It can be kind of difficult to predict what the computed style is when you're changing the transition style on hover, or when adding new classes just before/after a transition.

var getValues = function(str) {
  return str
    .replace(/[A-Z]/gi, "")
    .split(", ")
    .map(parseFloat);
};

var getMaxTransitionProp = function(el) {
  var style = window.getComputedStyle(el);
  var props = style.transitionProperty.split(", ");

  var delays = getValues(style.transitionDelay);
  var durations = getValues(style.transitionDuration);
  var totals = durations.map(function(v, i) {
    return v + delays[i];
  });

  var maxIndex = totals.reduce(function(res, cur, i) {
    if (res.val > cur) {
      res.val = cur;
      res.i = i;
    }
    return res;
  }, {
    val: -Infinity,
    i: 0
  }).i;

  return props[maxIndex];
}

var lastEventListenerFor = function(el, cb) {
  var lastProp = getMaxTransitionProp(el);
  return function(e) {
    if (e.propertyName == lastProp) {
      cb(e);
    }
  };
}

var a = document.querySelector("a");
var cb = function(e) {
  console.log("End");
};

a.addEventListener("transitionend", lastEventListenerFor(a, cb));
a {
  display: block;
  opacity: .5;
  width: 100px;
  height: 50px;
  background: lightblue;
  transition: 3s width,
  /* <-- "transitionEnd" should fire after this */
  2s height, .5s background;
}
a:hover {
  width: 200px;
  height: 100px;
  background: red;
}
<a>Hover me</a>

transitionEnd returns a property called propertyName in the event object. Source

Therefore you can test for the property you want and use simple logic to filter the callbacks:

document.querySelector('a').addEventListener('transitionend', function(event){
    if(event.propertyName !== 'width') return;
    console.log('transitionEnd - width!');
});

Tags:

Javascript

Dom