Whenever I need to pause the execution of my JavaScript code and allow the browser to update styles before continuing, I usually use the common technique of utilizing setTimeout
with a delay of 0
to queue a callback at the end of the event loop. However, I recently encountered a situation where this method doesn't seem to work consistently.
In the code snippet below, I have an active
class that triggers a transition effect on the chaser
element.
The scenario is that when I hover over a specific div element, I intend to remove the active
class from the chaser
element, reposition the chaser
, and then reapply the active
class. The expected outcome should be an immediate disappearance of the letter "o" followed by a fading in its new location. However, both the opacity
and top
properties undergo the transition, causing the "o" to slide between positions, most of the time.
By increasing the inner timeout delay to 10
, the behavior aligns more closely with my original intention. Setting it to 5
, however, results in sporadic outcomes.
I initially presumed that any setTimeout
call would queue my callback until after style updates have been processed, but there appears to be a race condition here. Am I overlooking something? Is there a way to ensure the correct order of style updates?
I am using Chrome 56 on macOS and Windows platforms; I have not conducted tests on other browsers yet.
(I acknowledge that there are alternative methods to tackle this issue, such as applying the transition solely to the opacity
property - for now, please treat this example as a means to address the query regarding style update ordering).
var targets = document.querySelectorAll('.target');
var chaser = document.querySelector('#chaser');
for (var i = 0; i < targets.length; i++) {
targets[i].addEventListener('mouseenter', function(event) {
chaser.className = '';
setTimeout(function() {
// At this point, no transition should be active on the element
chaser.style.top = event.target.offsetTop + "px";
setTimeout(function() {
// At this stage, the element ought to have completed moving to its new position
chaser.className = 'active';
}, 0);
}, 0);
});
}
#chaser {
position: absolute;
opacity: 0;
}
#chaser.active {
transition: all 1s;
opacity: 1;
}
.target {
height: 30px;
width: 30px;
margin: 10px;
background: #ddd;
}
<div id="chaser">o</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>
<div class="target">x</div>