[Proposal] Wait for element to transition

I think it would be helpful to allow for a developer to “wait” until an Element has fully transitioned after having its CSS transition property changed before continuing JS operations. So given the following html…

<html>

    <style>
    #my-element {
      transition: transform 320ms ease;
      transform: translateX(-100vw);
      /* more styles here.... */
    }
    
    #my-element.show {
      transform: translateX(0);
    }
    </style>
    
    <body>
        <div id="my-element"></div>
    </body>
    
</html>

We could wait for the element to finish its transition after adding the show css class to my-element in JS. Some ideas I have off-hand would be to:

Option 1

Update the add(), remove(), and toggle() methods of the Element.classList API to return promises whenever a new class is applied. The promise could resolve when the element has fully transitioned. But not sure if this is the best idea given that those are actually methods on the DOMTokenList interface and this feature wouldn’t be useful to other things that use it

let el = document.body.getElementById('my-element');
el.classList.add('show').then(() => {
    //.. the el has fully transitioned!
});

Option 2

Add a new wait method to the Element interface

let el = document.body.getElementById('my-element');
el.classList.add('show')
el.waitForElementTransition().then(() => {
    //.. the el has fully transitioned!
});

Option 3

Maybe even incorporate the adding or removal of a css class into the new method:

let el = document.body.getElementById('my-element');
el.addCssClassAndWaitForTransition('show').then(() => {
    //.. the el has fully transitioned!
});

But then we would have to have to introduce another method to remove the class also. The first parameter could also accept a style object or similar in case css style properties want to be used instead of a css class.

Option 4

Add a new Element.transition() method (similar to the animate() method in the Web Animations API) that accepts a css class and returns an Animation object.

let el = document.body.getElementById('my-element');
el.transition('show').finished.then(() => {
    //.. the el has fully transitioned!
});

I kinda of feel like there might be more options or an easier way to better integrate this into our existing APIs, though.

Would love any feedback on whether or not this would a good feature to add to the spec and possibly any recommendations on approach. Thanks in advance

EDIT: Added another option

Hi @mkay581,

Are you aware of the ‘transitionend’ event? It is widely supported, and should be possible to polyfill the other options.

Ian

You can also use getAnimations() from the Web Animations API if you’re looking for a Promise-based API for this. See the first example in https://w3c.github.io/web-animations/#use-cases.

@iank thanks for the reply. I’ve considered the transitionend event, however it has very inconsistent behavior and not guaranteed. AFAIK, it does not fire if the HTMLElement does not have a css transition property specified or if the element does not transition or have transition properties or if display is set to none. This makes it unreliable. I know because I’ve actually have created a waitForElementTransition lib where I’ve deliberately avoided relying on transitionend event for this reason.

@birtles thanks. I know that there is a Document.getAnimations() but wasn’t aware it was on the Element interface and supported CSS Transitions. I don’t see anywhere in the spec where it mentions it being on the Element interface. FWIW, Document.getAnimations() seems inefficient for this case because it would require looping over all transitions on the page. Wouldn’t this be too much if developer already knows exactly which element they’d like to wait for?

It will be a good idea above all because will emprove the browser performance alot but … it might be done in generic way for all rendering native functions.

Element has a getAnimations method by virtue of it implementing the Animatable interface.

The event bubbles up the DOM tree; this should be sufficient. For example, it is possible to get notified of finished transitions on descendant elements via event delegation. Regarding the display: none scenario, I’m not sure why the event does not fire in that case. There may be a good reason for that. Could you elaborate on why you need to listen for finished transitions on display: none elements?

My guess is that, since a display: none element is not rendered, there is no need for the browser to run the transition to begin with. Instead, the browser can just switch the element into the finished state immediately.

Yeah that’s true @simevidas. I guess since the goal is to have a more promise-based API (rather than the current event-based transitionend), the resolved state should still be triggered if the element’s display property goes from block to none, for example. If not, it would make a promise API inconsistent. Essentially, as a developer, I want to use a promised based API that is always guaranteed to resolve/reject and not have to worry about weird conditions in which it may not fire like the display:none. Hopefully that makes sense.

What if you run the animation in JavaScript instead of CSS, via the Element animate method? It returns an Animation object with a .finished promise. That seems like the standard solution to your problem.

Yup, I’m aware of the animate() method but it requires all animation css to be in JS. This is not ideal and does not keep concerns separate. I work in an environment where we want to keep css in a css file with all other css and placing css in JS is an anti-pattern.