JavaScript - Are Dom Redraw Methods Synchronous

Javascript - Are DOM redraw methods synchronous?

We can divide this "redraw" process in 3 parts, DOM update, Reflow, Repaint.

All these operations do not follow the same rules:

DOM update: Always synchronous. The DOM is just an other js object, and its manipulations methods are all synchronous.

Reflow: That's the strange beast you stumbled upon. This is the recalculation of all box positions of the elements on the page.

Generally, browsers will wait until you finished all DOM modifications, and thus, the end of the js stream, before triggering it.

But some DOM methods will force this operation, synchronously. e.g, all the HTMLElement.offsetXXX and alike properties, or Element.getBoundingClientRect, or accessing in-doc's Node.innerText or accessing some properties of getComputedStyle returned object (, and probably others) will trigger a synchronous reflow, in order to have the updated values. So beware when you use these methods/properties.

Repaint: When things are actually passed to the rendering engines. Nothing in the specs says when this should happen. Most browsers will wait the next screen refresh, but it's not said it will always behave like that. e.g. Chrome is known for not triggering it when you blocked the scripts execution with alert(), while Firefox will.

Is DOM rendering GUARANTEED to block during a single (synchronous) function's execution?

There is nothing in Javascript related to concurrency that is anything but de facto. JS simply does not define a concurrency model. Everything is happy accident or years of consensus.

That said, if your function does not make any calls to weird things like XMLHttpRequest or "alert" or something like that, you can basically treat it as single-threaded with no interrupts.

JavaScript not finishing DOM update and moving to next function

There are 2 things to know here:

  • When you use prompt (or its siblings alert and confirm), everything on the page stops until the box is dismissed. (other JS doesn't run, and the browser can't repaint.)

  • The browser waits to repaint the screen until the main thread's JavaScript execution is finished.

If you change the DOM, then immediately call prompt, the DOM update will not be seen until after the prompt finishes and the thread ends.

Here's a simpler example of this problem in action:

document.body.innerHTML += 'you will not see this until dismissing the prompt';
prompt('foo');

Does Javascript changing DOM always causes UI to update?

No, you can very well modify the DOM without causing any repaint.

Take for instance an element whose display is set to none. It's still in the DOM, but discarded by the CSSOM and won't cause any repaint if modified:

setTimeout(() => {  content_div.textContent = 'I got modified';  console.log(content_div.outerHTML) // "<div id="content">I got modified</div>"}, 1000);
#hidden {  display: none;}
<div id="wrapper">some hidden content below<div id="hidden">  <div id="content_div">I will get modified</div></div><div>

Is it possible to force a synchronous repaint in js?

Explanation of why screen updates are not possible in the middle of executing synchronous code may be found in the HTML spec:

8.1.4.1 Definitions

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section.

which implies rendering is a separate call out from the event loop in relation to call outs that execute scripts.

This is further detailed under the processing model:

8.1.4.2 Processing model

.... pick a task and run it [ steps 1-6]


  1. Update the rendering: If this event loop is a browsing context event loop (as opposed to a worker event loop), then run the following substeps.

....

Hence it is not possible to update the screen from within a browsing context in the middle of executing synchronous code in a single call out from the event loop.

Moving synchronous code to a web worker might form the basis of a solution (workers execute in a separate thread) but is outside the scope of this answer.

Is innerHTML asynchronous?

Setting innerHTML is synchronous, as are most changes you can make to the DOM. However, rendering the webpage is a different story.

(Remember, DOM stands for "Document Object Model". It's just a "model", a representation of data. What the user sees on their screen is a picture of how that model should look. So, changing the model doesn't instantaneously change the picture - it take some time to update.)

Running JavaScript and rendering the webpage actually happen separately. To put it simplistically, first all of the JavaScript on the page runs (from the event loop - check out this excellent video for more detail) and then after that the browser renders any changes to the webpage for the user to see. This is why "blocking" is such a big deal - running computationally intensive code prevents the browser from getting past the "run JS" step and into the "render the page" step, causing the page to freeze or stutter.

Chrome's pipeline looks like this:

Sample Image

As you can see, all of the JavaScript happens first. Then the page gets styled, laid out, painted, and composited - the "render". Not all of this pipeline will execute every frame. It depends on what page elements changed, if any, and how they need to be rerendered.

Note: alert() is also synchronous and executes during the JavaScript step, which is why the alert dialog appears before you see changes to the webpage.

You might now ask "Hold on, what exactly gets run in that 'JavaScript' step in the pipeline? Does all my code run 60 times per second?" The answer is "no", and it goes back to how the JS event loop works. JS code only runs if it's in the stack - from things like event listeners, timeouts, whatever. See previous video (really).

https://developers.google.com/web/fundamentals/performance/rendering/

What happens if we manipulate DOM in requestAnimationFrame?

whenever there is some DOM manipulation like inserting a DOM element would trigger a reflow and most likely followed by a repaint

The painting action occurs asynchronously, so "trigger" should be understood in that way. First your JavaScript code will finish before that actually happens.

if we somehow manage to do a DOM manipulation inside this rAF (edit: and also queue another rAF at the end) which triggers a reflow everytime and thus a repaint, we would be stuck in an infinite loop without actually rendering anything on the screen.

The needs for repaint accumulate and are not synchronously fulfilled. First your code has to complete until the call stack is empty. So there is no infinite loop here.

Or is it the case that once the browser had decided to do a repaint, it will stick with it and apply any updates that happened in the RAF callback in the next repaint?

Yes. When the RAF callback is called, that code gets a final chance to make updates to the DOM, which may accumulate further the needs for painting. If in that callback you also register another callback on RAF, it will not execute at that time, but later: at the next time that the browser will prepare its repaint task -- so not the current one.

Simplified example

Let's say you have this code:

requestAnimationFrame(update);

myElement.style.backgroundColor = "silver"; // This queues a need for repaint

function update() {
// This queues a need for repaint
myElement.style.width = Math.floor(Math.random() * 100) + "px";
requestAnimationFrame(update);
}

When this executes, we get the following sequence:

  1. update is registered as callback
  2. The background change schedules a need for repainting
  3. The callstack becomes empty
  4. The browser starts its repaint job, but takes into account there is a registered callback. So it removes this registration (because it should only run once) and executes update before doing anything else.
  5. The width change schedules a need for repainting. The list of changes now includes the background change and this width change and any cascade effect that has been calculated. (How this is represented is browser dependent)
  6. The update function is registered as callback again.
  7. The browser now checks what it needs to do as part of this repaint job, and performs all that is needed to visualise the effects of the background and width changes.
  8. The paint job ends. All that is left is the registered update callback.
  9. When the browser performs its next paint cycle, we start again from step 4, but now there is no queued background-change any more. For the rest it will be the same process.

DOM refresh on long running function

SOLVED IT!! No setTimeout()!!!

Tested in Chrome 27.0.1453, Firefox 21.0, Internet 9.0.8112

$("#btn").on("mousedown",function(){
$('#btn').html('working');}).on('mouseup', longFunc);

function longFunc(){
//Do your long running work here...
for (i = 1; i<1003332300; i++) {}
//And on finish....
$('#btn').html('done');
}

DEMO HERE!



Related Topics



Leave a reply



Submit