grafbumsdi
grafbumsdi

Reputation: 1015

Is it possible to check if a CSS Transition is finished WITHOUT using events?

Is it possible to somehow via JavaScript verify that a CSS-Transition is finished without previsouly registering for the transition events?

The problem is:

Is there any possibility to check via JavaScript if the CSS transition for the element is done? At any time?

I cannot make use of javascript events (like e.g: https://jonsuh.com/blog/detect-the-end-of-css-animations-and-transitions-with-javascript/)

Upvotes: 1

Views: 2683

Answers (3)

ibrahim tanyalcin
ibrahim tanyalcin

Reputation: 6491

IT IS POSSIBLE

TIMED CHAINS;

Sorry for the delay, was in a meeting. Searched for one of my old projects but could not find. I'll sketch the idea here, I initially thought perhaps we could go for Mutation Observer, however we have to do periodic checks there as well. So I guess this one will do. First you should avoid some things:

  • calling getComputedStyle every frame, it is a bad idea, because that is a very expensive func to call and triggers layout, so you should throttle instead.
  • hardcopying style object, that is a hefty object to copy, so instead you should pass an argument for a specific property
  • working with node reference, if the node is not there as you said, this will throw reference error, instead use a function to return the node.

The first thing is to write a helper function, a function that would periodically run some test function and return if it is successful:

 function watchman(obj,test,action,options){
    var currentFrame = {value:undefined};
    function watch(){
      if(test.call(obj,options)){
        action.call(obj,options);
        return;
      } else {
        currentFrame.value = window.requestAnimationFrame(watch);
      }
    };
    currentFrame.value = window.requestAnimationFrame(watch);
    return currentFrame;
  };

Next is the actual function, I'd say no need to create new object, we can make a function with 3 (2 optional) arguments, the node "functor", the style property to check and lastly the function to call.

 function onTransitionEnd(fNode,prop,f,precision,timeout){
    precision = precision || 5;
    timeout = timeout || Infinity;
    return new Promise(function(res){
      var node = fNode(),
          oValue = node && getComputedStyle(node)[prop],
          currentFrame = watchman(
            fNode,
            function(counter){ 
              if(counter.counter * 17 >= timeout){
                window.cancelAnimationFrame(currentFrame.value);
              }
              if(++counter.counter % precision === 0) {
                if(!this()){return}
                var nValue = getComputedStyle(this())[prop];
                if(nValue === oValue) {
                  return true;
                }
                oValue = nValue;
              }
            },
            function(counter){
              res(f.call(fNode(),prop));
            },
            {counter:0}
          );
    });
  };

Default precision of 5 means the function will check every 5 ticks, 5 * 17 milliseconds the values to determine if transition ended. Timeout is optional too, it will cancel running after a certain period.

It is not a problem if the node is NOT there, as we are passing a function that returns the node or null, if the node is not there, it will not execute.

Above is a promise and would return a "thenable" object which you can chain as you like.

Simple use case, for instance right after you change a style or class:

document.getElementById("someDiv").className = "def c1";
onTransitionEnd(
  function(){return document.getElementById("someDiv");},
  "transform",
  function(){alert("heyy!!");}
);

it would now alert you "heyy". To chain this:

document.getElementById("someDiv").className = "def c1";
onTransitionEnd(
  function(){return document.getElementById("someDiv");},
  "transform",
  function(prop){alert("heyy!!"); return this;}
).then(function(node){
    node.className  = "def";
    return onTransitionEnd(
    function(){return document.getElementById("someDiv");},
    "transform",
    function(){alert("heyy-2!!"); return this;}
  );
}).then(function(node){
    alert("id is " + node.id);
});

Here are some examples:

for the last one to work, open the developer console, select the blue div and change its id to "someDiv", the function will execute.

You might wonder whether to call onTransitionEnd each time you change the style, in that case you can write a wrapper. If you don't have an idea let me know I'll write that too.

Obviously you did not use a wrapper, so here is a helper wrapper:

function Select(node){
  this.node = node;
};
  Select.prototype.style = function(prop,value,f){
    var node = this.node;
    this.node.style[prop] = value;
    f && onTransitionEnd(
      function(){return node;},
      prop,
      f
    );
    return this;
  };

Here is how you would use it:

var selection  = new Select(document.getElementById("someDiv"));
selection
.style("width","100px",function(propName){alert(propName + " changed!");})
.style("height","100px",function(propName){alert(propName + " changed!");})
.style("transform","scale(0.5,0.5)",function(propName){alert(propName + " changed!");});

And here is the EXAMPLE;

TIMED CHAINS;

Upvotes: 0

Thomas
Thomas

Reputation: 12637

Not an Answer but a quick POC to build upon:

element.onclick = function() {
  const color = 0x1000 | (Math.random() * 0x1000);
  
  const prop = Math.random() < .5? "background-color": "color";
  
  element.style[prop] = "#" + color.toString(16).slice(-3);
}

let curr = {};
requestAnimationFrame(function check() {
  const prev = curr;

  curr = Object.assign({}, getComputedStyle(element));

  const changed = Object.keys(curr).filter(key => curr[key] !== prev[key]);

  out.innerHTML = changed.map(key => `<li>${key}</li>`).join("\n");

  requestAnimationFrame(check);
});
html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

#element {
  cursor: pointer;
  width: 100%;
  height: 100%;
  padding: 20px;
  transition: all 1s;
}
<div id="element">
  <p>Click somewhere</p>

  currently transitioning:
  <ul id="out"></ul>
  
</div>

you'll notice the flickering, that's because two adjacent frames might not differ during that interpolation. You'd want to cache more frames and compare something like 5-10 frames apart; depends on the interpolation method you use and the duration of a transition.

Besides that, depending on the properties you're checking you could also compare getComputedStyle(element)[key] against element.style[key] instead of storing the values for multiple frames. But this doesn't work out for color (and others), as there are so many ways to describe the same color.

Upvotes: 0

Ealhad
Ealhad

Reputation: 2220

No.

The best you can do is looking at the CSS to see the transition duration.

Upvotes: 4

Related Questions