0

So Im creating something where I need to be able to transform some elements with and without transition.

Lets say that i have to transform something without transition, then i ofcourse firstly remove the transition parameter (if there's any) from the element, whenever i know the transition styling is gone i would like to transform the element. And maybe after that i would like to transform the element again, but this time with some transition. So first, I add the transition, and whenever thats ready i transform the element.

I've created a function "setStyle" which is supposed handle the style setting for a given element, and then resolve whenever that style is actually added to the view. I've tried to do so with the help of the MutationObserver. But ive clearly misunderstood something about this, or the use case of MutationObserver.

I've created a simple CodePen with the function "setStyle" where i demonstrate that i first use the function to set a transform style attribute, and since there's no transition attribute added just yet, this shouldnt be transitioned. After ive added the transform attribute i add the transtition attribute, but in this code example it looks like the two attributes are applied at the very same time.

Why do i not just use "setTimeout"? "setTimeout" is not garuanteed, I would like to do something that work with any web API.

https://codepen.io/herman-jansson/pen/NWNKWXL

<button id="start">Start</button>
<div id="div">

</div>
var elem = document.getElementById('div');

var setStyle = (element, style, value) => {
    return new Promise(resolve => {
      const observer = new MutationObserver((mutations) => {
        if(
          document.contains(element) 
          && 
          mutations.some((mutation) => !!mutation.attributeName)
        ) {
          // style should be added here, but most likely not calculated since 
          // its not working
          observer.disconnect();
          resolve();
        }
      })
       observer.observe(element, { attributes: true, attributeFilter: ["style"]});
       element.style[style] = value;
    });
}


document.getElementById('reset').addEventListener('click', () => {
    setStyle(elem, 'transform', 'translateX(-50%)').then(() => {
      setStyle(elem, 'transition', 'transform 1s ease-in-out')
    });
});
Herman Jansson
  • 164
  • 1
  • 8

1 Answers1

1

While the style attributes did get updated in two discrete steps (as shown by adding debug code to the MutationObserver), the render engine doesn't immediately apply each style as it's set, instead letting them accumulate and then applying them all at once. Your MutationObserver just sees that the attribute was updated (but not if the render engine applied the change) and resolves immediately. By the time the render engine gets around to acting on the style changes, both changes have been made and are seen as part of the same update.

One solution is to trigger a reflow yourself between the two style attribute updates. (This is not normally something you would want to do as it can make the page less responsive and so should be applied with consideration and caution.) Generally, reading (directly or indirectly) a computed dimension or position of an element will cause a reflow. This gist lists a number of attributes and functions that are known to cause immediate reflow.

In summary, adding a line like element.offsetLeft; so that it is executed between the two style updates may appear to do nothing but causes reflow as a side-effect. This in turn would cause the render engine to treat the two style changes as separate updates.

I believe you may be able to even go so far as to remove the setStyle function and just go with:

elem.style.transform = 'translateX(-50%)';
elem.offsetLeft;
elem.style.transition = 'transform 1s ease-in-out';
Ouroborus
  • 16,237
  • 4
  • 39
  • 62