Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine when a browser has finished painting an inserted element?

Tags:

javascript

I have a web page that is injecting HTML delivered via AJAX. I don't know, a priori, the size of the content, but I need to position the content on the page based on its size. Here's a simple approach that doesn't work:

  1. Set parent container's opacity to 0.
  2. Insert the content into the DOM.
  3. Measure the sizes, e.g. $el.outerWidth(true).
  4. Position the content based on those sizes.
  5. Set the parent container's opacity (back) to 1.0.

The problem with this approach is that the content is quite complex and includes images. When outerWidth() is called, the browser hasn't yet finished repainting the screen so that the sizes returned aren't accurate. (Interestingly, in my case the initial dimensions are always significantly larger than the ultimate values, not 0 as I might expect.)

I can reduce the problem by setting a timer between steps 2 and 3, but no matter how long I make the timer, there will surely be some browser on some system that will exceed it. And if I make the timer long enough to cover most cases, that will penalize users with faster systems by introducing unnecessary delays. Although a few of the answers referenced below suggest a setTimeout(fn, 0) is sufficient, I've found that not to be be the case. I've measured paint times as high as 60 ms on a 2012 MacBook Air, and one answer below suggests 500 ms.

It looks like Mozilla at one time had an onPaint event that might have solved my problem … if it still existed and if other browsers had adopted it. (Neither of which is the case.) And Mutation Observers don't seem to account for paint time when reporting changes in elements.

Solutions or comments very much appreciated. As noted below, this has been asked before but most of the questions are at least a year old and I'm desperate enough to try again.


Related questions that don't, unfortunately, offer a workable answer

  • How to detect when an image has finished rendering in the browser (i.e. painted)?
  • jQuery - How to execute script as soon as we know true height (with images) of ajax-loaded element?
  • DIV width immediately after append is calculating wrong?
  • jQuery returns height of 0 for div element immediately after appending element
  • Javascript: unable to get height of newly created element
  • jQuery: Why does .width() sometimes return 0 after inserting elements with .html()?

Update: Well, it appears there simply isn't a general solution for this problem. In my specific case, I found a property that would reliably change once the content was loaded and layout was complete. The JavaScript polls for a change in this property. Not at all elegant, but it was the best option I could find.

like image 872
Stephen Thomas Avatar asked Nov 23 '25 13:11

Stephen Thomas


1 Answers

I've started looking at the current situation of this and my solution would be this:

doSomeDomManipulation();
requestAnimationFrame(() => {
  setTimeout(() => {
    alert("finished painting");
  }, 0);
});

Source for this approach:

This used to be inside a note on the old MDN page on the topic of MozAfterPaint:

"Web pages that want to take an action after a repaint of the page can use requestAnimationFrame with a callback that sets a timeout of zero to then call the code that takes the desired post-repaint action."

Edit: An even better version that uses queueMicrotask instead of setTimeout (setTimeout queues a macro task, which would run later):

doSomeDomManipulation();
requestAnimationFrame(() => {
  queueMicrotask(() => {
    alert("finished painting");
  });
});
like image 85
Marko Knöbl Avatar answered Nov 26 '25 03:11

Marko Knöbl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!