3

Try running the following code in window.onLoad:

var n = 0;
var N = 75000;
while (n < N)
{
    span = document.createElement("span");
    span.innerHTML = "0";
    document.body.appendChild(span);
    n++;
}
console.log("Finished.");

You should find that "Finished" appears in your console several seconds before the span tags appear in your browser. If not, try increasing N by a few orders of magnitude.

How can I make DOM changes, and detect the moment at which not only the changes are complete, but are rendered on-screen?

I tried a MutationObserver, but it also gets notified several seconds before the changes appear onscreen. You can see that in this fiddle.

Jack M
  • 4,769
  • 6
  • 43
  • 67
  • You could just wait for the next event loop,.. `setTimeout(function () { console.log("done"); }, 1);` – Keith Mar 15 '18 at 12:02
  • 2
    "How can I make DOM changes, and detect the moment at which not only the changes are complete, but are rendered on-screen?" — This sounds like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Why would that information be useful to you? – Quentin Mar 15 '18 at 12:05
  • 1
    @Quentin My extension makes extensive changes to the DOM which take a few seconds on large pages/slow computers, and I want a "spinning wheel" progress indicator that goes away once the changes are *rendered*, not just made in code. – Jack M Mar 15 '18 at 12:10
  • You could try a transitionend event on the last element, but I'm with @Quentin, you should rather be looking at how to improve your perfs. If the rendering is blocked, your spinning thing won't spin. – Kaiido Mar 15 '18 at 12:13
  • I would have expected that a bunch of changes made to the DOM, where the last one was the removal of a spinning wheel, would result in the rendering update that removed the wheel not happening before the other changes. – Quentin Mar 15 '18 at 12:15
  • @Quentin Not necessarily in the world of extensions, where several different runtime contexts run in parallel (background and page). The progress indicator icon may not be in the DOM itself, it may be in the extension toolbar button. Also, I may want to do other things than change the DOM (remove the spinning wheel) in response to rendering completion, like setting a flag. I'd like a reliable way to invoke a callback when rendering completes. – Jack M Mar 15 '18 at 12:18
  • Do your DOM in smaller batches, inside a setTimwou(fn,0) loop or a requestAnimationFrame loop and at the final batch hide your wheel. And if you really really want to block UI and possibly trigger a "scrpit takes too long" you can try https://stackoverflow.com/questions/32085700/jquery-append-css-file-and-wait-until-styles-are-applied-without-setinterval/32085957?s=1|27.3146#32085957 – Kaiido Mar 15 '18 at 12:22

1 Answers1

1

I have little experience with such operations, but 5 minutes of experimentation with requestAnimationFrame suggests that it might be useful for such use-case.

According to MDN:

The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.

So I had a hunch that if a particular render took too long, the next call to the callback passed into requestAnimationFrame would be delayed.


I have added a div (id = "loading") to your fiddle, which is visible initially. After appending all the nodes to DOM, I pass a callback to requestAnimationFrame that would hide the #loading div from the screen.

var loading = document.getElementById("loading");

// Update DOM ...

requestAnimationFrame(function() {
    loading.style.display = "none";
});

You can take a look at the the fiddle here.

Nisarg Shah
  • 14,151
  • 6
  • 34
  • 55
  • When I run that fiddle, the "Animation done" message appears long before the spans appear on screen. – Jack M Mar 15 '18 at 13:06
  • @JackM It does actually appear before the numbers appear on screen. Possibly because the callback that hides "loading" is supposed to execute **with** the render that displays all those numbers. You can actually wait for another repaint by adding another `requestAnimationFrame` in between, but then you would start seeing the "loading" text while the numbers have started rendering. In this case, "loading" will be hidden **after** the numbers have completed rendering. Like this: https://jsfiddle.net/6d6tkshc/23/ – Nisarg Shah Mar 15 '18 at 13:13