Question

Looking for a solution or even just some "under the hood" theory behind what's going on in the following examples I put together.

It appears that manipulation of the DOM happens in a non-blocking fashion. In other words, there is no flow-control to wait for the execution of DOM statements.

In this example: http://jsfiddle.net/qHVJX/

Two statements are executed after an onclick event.

  1. shift text left to right

    document.getElementById('text').className = 'tr';
    
  2. loop a bunch of time to halt execution.

    for(var i = 0,temp=1000000000; i<temp;i++){var dummy=0;} 
    

If flow-control was blocked while each statement executed then, as expected, the text would shift left to right followed by a short pause from the loop. Instead, the text shift happens after the loop iterates.

This suggests that the className statement gets queued while the loop takes execution priority.

In this example: http://jsfiddle.net/qHVJX/1/

Three statements are executed after an onclick event:

  1. shift text left to right

    document.getElementById('text').className = 'tr';
    
  2. loop a bunch of time to halt execution.

    for(var i = 0,temp=1000000000; i<temp;i++){var dummy=0;}
    
  3. shift text right to left.

    document.getElementById('text').className = 'tl';
    

Again, If flow control was blocked while each statement executed then, as expected, the text would shift left to right followed by a short pause while the loop iterated, followed by the text shifting from right back to left.

Instead, the loop is executed and the repaint of the text from left to right and right to left never occurs. This illustrates a race condition.

setTimeout() could halt execution long enough for the DOM statement to do it's thing and avoid being queued, however; there is no way to know how long each statement is going to take. if the interval is set to short than it wont achieve the desired results. If the interval is set to be longer than it needs to be then performance suffers.

Is there anyway to block execution during DOM manipulation without the overhead of setTimeout()? Some sort of callback routine perhaps?

Any insight would be much appreciated. Thanks.

Was it helpful?

Solution 2

Not sure what you mean by "the overhead of setTimeout()", but calling setTimeout with the timeout of 0 seems to be the commonly suggested way of doing this.

http://javascript.info/tutorial/events-and-timing-depth#the-settimeout-func-0-trick

OTHER TIPS

I realize this is an old question, but I want to explain why the loop is halting the change of the text to the right.

The browser refreshes the window at a maximum rate of 60 times per second. This will be done through the event loop, which also schedules other operations. First, all the sync code is ran. Then, event loop will go through the deferred operations; including the ones affecting the window repaint (e.g., changing a class in the DOM element and applying the styles).

That's what is happening here. When you trigger the event, the sync code will be done first. That is:

1) assign tr as the classname to the DOM element data (but not to the displayed DOM itself. In other words, if you check the elements on the dev tools, you won't see the change at this time).

2) run the loop.

After the sync code is done, the event loop will see what else needs to be done. The repaint will happen here. So, it is at this time that you will see the element on the dev tools acquiring the tr class and the text shifting to the right.

The best way to schedule operations to be done at this time is through requestAnimationFrame (and not necessarily setTimeout). As such:

document.getElementById( 'text' ).className = 'tr';

requestAnimationFrame( () => {
  // ...
} );

I've changed your code to illustrate better what I'm saying:

document.addEventListener( 'mousedown', shift_text, false );

function shift_text() {
    requestAnimationFrame( () => {
        console.log( 'requestAnimationFrame 1' )
    } );

    // execute before halt
    document.getElementById( 'text' ).className = 'tr';

    console.log( 'class name now is', document.getElementById('text').className );

    requestAnimationFrame( () => {
        console.log( 'requestAnimationFrame 2' )
    } );

    // halt with large loop
    for(var i = 0,temp=3000000000; i<temp;i++){var dummy=0;} 

    console.log( 'sync code is done' );
}

If you run this, you'll see that the console prints will be:

class name now is tr // Despite the text still being on the left.
sync code is done // After a while due to the large loop.
requestAnimationFrame 1 // At this point, the text will be on the right.
requestAnimationFrame 2

Clarifying your questions, each statement is blocked, indeed. Each call to requestAnimationFrame will be executed at that time, but the callback passed as argument is scheduled (it won't be executed right away). This is the reason why the text doesn't shift before the loop. That being said, and as showed by my example code, the classname assignment statement is not scheduled, because you can see the data updates and the class name is at that time tr. But the display of that change is indeed deferred.

Regarding your three statements suggestion, if you had document.getElementById('text').className = 'tl'; after the for loop, since this is sync code, this will change the class back to tl. So when the repaint kicks in, the class name is tl. In other words, you changed the class to tr, then to tl and then the repaint occurs, with the class being tl. I think my code example clarifies this, because you can see the first console.log will clearly say the class is tr. The rendering might never use such value, if you change it back, before the rendering is done.

Hope this helps clarifying what's happening!

Also see: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

setTimeout is absolute the correct way also if it's just a Resolution of a few milliseconds.

Your Loop is just blocking the Browsers update. Also it's not a predictable Duration the Loop takes for processing. concerning an intelligent JavaScript Compiler it the Loop could be omitted due optimization if there are no benefits due Looping.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top