CSS transition doesn't start/callback isn't called
For a comprehensive explanation of why this happens, see this Q/A, and this one.
Basically, at the time you set the new style
the browser still has not applied the one set inline, your element's computed style still has its display
value set to ""
, because it's what elements that are not in the DOM default to.
Its left
and top
computed values are still 0px
, even though you did set it in the markup.
This means that when the transition
property will get applied before next frame paint, left
and top
will already be the ones you did set, and thus the transition will have nothing to do: it will not fire.
To circumvent it, you can force the browser to perform this recalc. Indeed a few DOM methods need the styles to be up to date, and thus browsers will be forced to trigger what is also called a reflow.Element.offsetHeight
getter is one of these method:
let tablehtml = `
<div id="spanky"
style="position: absolute;
left: 10px;
top: 10px;
background-color:blue;
width:20px;
height:20px;
transition: left 1000ms linear 0s, top 1000ms linear 0s;">
</div>`;
document.body.innerHTML += tablehtml;
let animdiv = document.getElementById('spanky');
animdiv.addEventListener("transitionend", function(event) {
animdiv.style.backgroundColor='red';
}, false);
// force a reflow
animdiv.offsetTop;
// now animdiv will have all the inline styles set
// it will even have a proper display
animdiv.style.backgroundColor='green';
Object.assign(animdiv.style, {
left: "100px",
top: "100px"
});
it has to do with the timing of when the new element is actually "painted" ...
I know this is a kludge too but ...
the one way I've found to guarantee 100% success is to wait just under two frames (at 60fps that's about 33.333ms - but the setTimeout
in browsers these days has a artificial "fudge" added due to spectre or something - anyway ...
requestAmiationFrame(() => requestAnimationFrame() => { ... your code ...}))
does the same thing except it could be as little as 16.7ms delay, i.e. just over one frame
let tablehtml = `
<div id="spanky"
style="position: absolute;
left: 10px;
top: 10px;
background-color:blue;
width:20px;
height:20px;
transition: left 1000ms linear 0s, top 1000ms linear 0s;">
</div>`;
document.body.innerHTML += tablehtml;
let animdiv = document.getElementById('spanky');
animdiv.addEventListener("transitionend", function(event) {
animdiv.style.backgroundColor='red';
}, false);
requestAnimationFrame(() => requestAnimationFrame(() => {
animdiv.style.backgroundColor='green';
Object.assign(animdiv.style, {
left: "100px",
top: "100px"
});
}));
having a single requestAnimationFrame
failed about 1 in 10 for me, but the double request makes it impossible to fail