Reputation: 1015
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
Reputation: 6491
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:
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;
Upvotes: 0
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
Reputation: 2220
The best you can do is looking at the CSS to see the transition duration.
Upvotes: 4