When invoking setTimeout(fn,0), the function runs before the CSS styles have been fully applied

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>

Answer №1

Make sure to pay attention to the transitionend event before proceeding with any other actions. For more information on the transitionend event, you can visit this MDN link. Additionally, it is advised not to rely on setTimeout for timing precision.

UPDATE: Following clarification from OP, it is important to note that whenever a style change occurs on an element, there will be a reflow and/or repaint. More details on this topic can be found in this resource. Timing plays a crucial role in achieving the desired sliding effect, as explained by the use of two setTimeout functions and adjusting the property values.

In summary, the browser checks for updates from JS and CSS every 16ms to render content. Missing this window can result in significant differences in the final display.

Answer №2

Instead of nesting setTimeout() timing methods, consider calling them separately for better control over execution.

Execute the first setTimeout() method first, then upon completion, trigger the second setTimeout() method.

Answer №3

Below is a functional code snippet to make the follower element move:

function _( id ) { return document.getElementById( id ); }

window.addEventListener( 'load', function() {

var objects = document.querySelectorAll('.object');
var follower = document.querySelector('#follower');

setTopPosition( objects );

function setTopPosition( objects ) {
    for (var i = 0; i < objects.length; i++) {
        objects[i].addEventListener('mouseenter', function(event) { 

            follower.className = '';
            _( 'status' ).innerText = follower.className; // to check the active class

            setTimeout(function() {
                /* I expect no transition to be active on the element at this point */

                follower.style.top = event.target.offsetTop + "px";
            }, 0); 

            if ( follower.className == '') {
                setClassName();
            } else {
                alert( 0 );
            }

        });
    }
}

function setClassName() {
    setTimeout(function() {

         /* At this point, I'm expecting the element to have finished moving to its new position */

        follower.className = 'active';
        _( 'status' ).innerText = follower.className;
    }, 0);
}

});

HTML:

<div id="follower">o</div> 

<div class="object">x</div> 
<div class="object">x</div> 
<div class="object">x</div> 
<div class="object">x</div> 
<div class="object">x</div>

<div id="status">status</div>

CSS:

#follower { position: absolute; opacity: 0; }
#follower.active { transition: all 1s; opacity: 1; }
.object { height: 30px; width: 30px; margin: 10px; background: #ddd; }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way for me to trigger the event multiple times?

First of all, thank you for your input - this is my initial attempt at solving this issue. Currently, I am facing a problem where the buttons only work once. Ideally, I want them to be functional every time because users will frequently change search terms ...

Retrieve JSON Object using a string identifier

I created a script that takes the ID of a link as the name of a JSON dataset. $('.link').click(function() { var dataset = $(this).attr("id"); for (var i = 0; i < chart.series.length; i++) { chart.series[i].setData(lata.dataset ...

Trouble encountered while trying to assign text to an option within a jQuery loop

Here is the code I am working with: $('#selectid option').each(function(){ if( this.text.indexOf("abcxyz") >= 0){ var lable = this.text.replace("abcxyz", ""); this.text(lable); ...

How come I am unable to expand a collection of objects using Zustand?

I am currently utilizing Zustand in a TypeScript Next.js application. Strangely, whenever I attempt to loop through my state object, I encounter a runtime error message. The structure of the damaged zone object for my car is as follows: const damagedZones ...

Utilizing Restangular to assign multiple parameters to a variable within the scope variable

Currently learning AngularJS and utilizing Restangular to communicate with a Rails server API. Struggling with grasping the concept of assigning form parameters to a variable in order to use it within my function for posting to the Rails API. Below is an ...

Dynamically load values for AJAX functions

I am working on a dynamic list of strings that are generated through an AJAX call. Here is the function I am using: function updateIngredients(boxId, prodid) { $.ajax({ type: "GET", url: "/Products/FetchBoxIngredients?idbox ...

"Transforming a simple object into an instance of a different type in JavaScript: A step-by-step guide

Having an issue storing a session on disk for local development. The application is asking for an instance of Session to be returned, not an Object function storeCallback(session) { console.log("storeCallback ", session); fs.writeFileSync(&qu ...

Templating with Underscores: Revolutionizing token markers

When using out of the box underscore templating, the default markers for raw content are <%= %>, and for HTML escaped content are <%- %>. However, it is possible to change these markers by adjusting the template settings, for example: _.templ ...

What is the best way to iterate through a JSON array of various cookies using a for...in loop to extract the key value pairs?

I could be approaching this in the wrong way, so I welcome any feedback and suggestions... My current approach involves saving panel states in a custom UI by utilizing cookies during the click function with the js-cookie (formerly jquery-cookie) plugin. T ...

Effortless JavaScript function for retrieving the chosen value from a dropdown menu/select element

I've figured out how to retrieve the value or text of a selected item in a dropdown menu: document.getElementById('selNames').options[document.getElementById('selNames').selectedIndex].value In order to simplify this code, I&apos ...

Issue with responsive canvas height in Particle-JS

Recently, I've been working on a Bootstrap portfolio where I am experimenting with implementing particle.js over a profile picture. I placed the particles inside a DIV and set a background image as the profile photo. The issue arises when I reload th ...

VBA Hover Clickthrough Technique

I have successfully logged in, but I am having difficulty clicking on the hover image/text. While I can use the base URL and the href provided in the source code to navigate, I would need to re-enter login information. Additionally, the page that the URL n ...

Filter JSON object array based on multiple criteria using JavaScript

users = [ { "username": "Alice", "firstName": "Alice-U", "lastName": "Wonderland" }, { "username": "bob", "firstName": "Bob-u", "lastName": "Builder", }, { "username": "charly", "firstName": "Charly-u", "lastNa ...

Vuejs: Users can still access routes even after the token has been deleted

Upon logging out of my application, I have encountered a peculiar issue. Certain inner links, such as those within a user's panel, remain accessible even after I have logged out and deleted a token. However, other links are not. Any suggestions on how ...

Unexpected problem with special characters on Google Font Ubuntu rendering improperly

I'm looking to showcase text using the Ubuntu Google font. However, when I try to include special characters in a simple webpage, it doesn't display properly. For example: touché – ‘çá’ Interestingly, when I attem ...

Encountering an error with React Redux: TypeError - Unable to access the 'props' property due to its undefined nature

After transitioning my code from Class-Based Components to Function-Based Components, I encountered an issue. While the code worked perfectly fine in Class-Based Components, I started receiving a TypeError: Cannot read property 'props' of undefin ...

Utilizing repl.it for a database in Discord.js

I've created a database script on repl.it and it seems to be functioning properly. However, I keep encountering null values in the users' database. Here's my code snippet: client.on("message", async (message) => { if (messag ...

Utilizing negative margins in Bootstrap for row positioning

The design of Bootstrap rows includes a margin (left and right) of -15px. This is primarily due to two factors: The .container has a padding (left and right) of 15px The col-* classes include a gutter of 15px. To prevent the empty space caused by the g ...

Manipulating intricate CSS properties with JavaScript

Can anyone explain how to implement the CSS shown below in JavaScript? #absoluteCenter { left: 50% !important; top: 50% !important; -webkit-transform:translate(-50%,-50%)!important; -moz-transform:translate(-50%,-50%)!important; -ms-tr ...

Angular's use of ES6 generator functions allows for easier management of

Recently, I have integrated generators into my Angular project. Here is how I have implemented it so far: function loadPosts(skip) { return $rootScope.spawn(function *() { try { let promise = yield User.findAll(); $time ...