2

How to detect via js if any sort of transition is being applied to the element right now?

Short story of my problem:

I have a situation where I'm firing a function on the `transitionend` event, but sometimes the element doesn't have any *transition* being applied (because in Firefox, for example, the user might click some element rapidly which makes the transition goes crazy and stop working) so I want to know when it doesn't work and just fire the function myself, skipping the `transitionend`. I am trying to avoid ugly solutions..
vsync
  • 118,978
  • 58
  • 307
  • 400
  • When your problem includes any specific browser, and the words "goes crazy and stops working" I don't know that there is a pretty solution... – Ryan Jan 15 '13 at 23:33
  • the specifics of the problem is not really an issue, and it's on all browsers.. i just gave a very specific example for the curios folks, but my question stands and it's something I would like to know even if I hadn't faced a problem. – vsync Jan 15 '13 at 23:45
  • I know, I was just giving you a hard time. I would actually like to know as well. I've run into problems with css-transitions before, as is evidenced by this question: http://stackoverflow.com/questions/7288977/element-style-display-none-not-working-in-firefox-but-is-in-chrome – Ryan Jan 15 '13 at 23:48

3 Answers3

2

You can use the Web Animation API for that, notably the Element#getAnimations method, which will return a list of Animation objects applied on the Element. These will include Web Animations (from .animate()), CSS @keyframes animations, and CSS transitions.

document.querySelectorAll("a").forEach((el) => {
  el.onmouseenter = (evt) => {
    const animations = el.getAnimations(); // we could also ask to look in the subtree
    // we're only interested in CSS transitions
    const transitions = animations.filter((anim) => anim instanceof CSSTransition);
    console.log(transitions.length
      ? transitions.map((anim) => anim.transitionProperty )
      : "no transition"
    );
  };
});
a:hover {
  color: red;
  opacity: 0.5;
}
.color-transition {
  transition: color 1s;
}
.color-and-opacity-transition {
  transition: color 1s, opacity 5s;
}

/*SO-only*/.as-console-wrapper { max-height: 110px !important }
<b>Hover these anchors to log their applied transitions.</b><br>
<a class="color-transition">color transition</a><br>
<a class="color-and-opacity-transition">color &amp; opacity transition</a><br>
<a>no transition</a>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
0

You can listen to transitionstart, transitionend, and, transitioncancel events. To tell if some element is under transition. However, you cannot know if some element will start a transition (even if it has transition-delay: 0s) using this code:

/** @type {Map<HTMLElement, number>} */
const transitionCounter = new Map();
/** @type {(() => void)[]} */
const waitingTransition = [];

const incReference = (counter, target) => {
  if (counter.has(target)) {
    counter.set(target, counter.get(target) + 1);
  } else {
    counter.set(target, 1);
  }
};
const desReference = (counter, target) => {
  if (!counter.has(target)) {
    return;
  } else if (counter.get(target) === 1) {
    counter.delete(target);
  } else {
    counter.set(target, counter.get(target) - 1);
  }
};
document.addEventListener('transitionstart', event => {
  const { target } = event;
  incReference(transitionCounter, target);
  const onFinish = event => {
    if (event.target !== target) return;
    desReference(transitionCounter, target);
    target.removeEventListener('transitioncancel', onFinish);
    target.removeEventListener('transitionend', onFinish);
    [...waitingTransition].forEach(listener => { listener(); });
  };
  target.addEventListener('transitioncancel', onFinish);
  target.addEventListener('transitionend', onFinish);
});

/**
 * @param {HTMLElement} element
 * @returns {boolean}
 */
const isUnderTransition = function (element) {
  const parents = [];
  for (let i = element; i; i = i.offsetParent) parents.push(i);
  return Array.from(transitionCounter.keys()).some(running => parents.includes(running));
};

/**
 * @param {HTMLElement} element
 * @returns {Promise<void>}
 */
const waitTransitionEnd = async function (element) {
  if (!isUnderTransition(element)) return Promise.resolve();
  return new Promise(resolve => {
    waitingTransition.push(function listener() {
      if (isUnderTransition(element)) return;
      waitingTransition.splice(waitingTransition.indexOf(listener), 1);
      resolve();
    });
  });
};
tsh
  • 4,263
  • 5
  • 28
  • 47
-1

As specified by W3C Editor's Draft - CSS Transition

The ‘transitionend’ event occurs at the completion of the transition. In the case where a transition is removed before completion, such as if the transition-property is removed, then the event will not fire.

So, I think there's not a valid simple way to solve this problem. The solution is left to the implementation (the browser) which decide if it does or doesn't render the transition at all.

Maybe, a solution could be to attach a listener to the element that fires the transition and after a specific elapsed time it checks if the transitioned element has the required CSS attributes set, and if those attributes aren't set as expected you can run your function by yourself.

Ragnarokkr
  • 2,328
  • 2
  • 21
  • 31
  • yes I've thought about using some setTimeout method but..it's just ugly. i'm very surprised there is no "ongoingTransition" flag in the W3C draft or something of the sort. – vsync Jan 16 '13 at 00:13
  • yes, it's a big limit, I hope when they will reach the final specifications, more events will be available. But currently, I guess the only way is to rely on custom timed functions which simulate the missing event callbacks. – Ragnarokkr Jan 16 '13 at 00:26
  • that means synchronizing the timing of the CSS and the JS manually..tedious. – vsync Jan 16 '13 at 00:59