47

On a web app I need to work with a lot of high-res images, which are dynamically added to the DOM tree. They are pre-loaded, then added to the page.

It's one thing to detect when such an image has finished loading, for this I use the load event, but how can I detect when it has finished being rendered within the browser, using Javascript? When working with high resolution images, the actual painting process can take a lot of time and it's very important to know when it ends.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Andrei Oniga
  • 8,219
  • 15
  • 52
  • 89
  • 1
    Do you actually encounter a noticeable difference between the `.load` event of the image and the actual rendering? – Ja͢ck Jan 29 '13 at 08:24
  • 4
    @Jack Very much so, I'm afraid. On very large resolution images, at least. – Andrei Oniga Jan 29 '13 at 08:25
  • 2
    I think only Firefox has [paint events](http://ejohn.org/blog/browser-paint-events/) which could be something that you could use. – Ja͢ck Jan 29 '13 at 08:26
  • 1
    Oh, don't tell me that! :( I was afraid that this is what I would hear. Is there no cross-browser solution? – Andrei Oniga Jan 29 '13 at 08:36
  • That's exactly right. I am after object:resized event (fired multiple times as the edge of the object is dragged by mouse), but in the absence of that in all browsers, the next best may be to track after:rendering event. I have seen it used by someone else. This has nothing to do with loading of the object. I can't use object:changed event. That happens only at the end (only once). – Yoichi Dec 13 '13 at 20:44
  • I'm curiuos: is this on any specific device? And how high are the resolutions? – Shomz Apr 04 '14 at 11:24
  • I have tried on latest stable versions of Chrome and FF, and there doesn't seem to be any delay between the image finished painting and the load() event triggered: http://jsfiddle.net/cbhJn/7/embedded/result/ – adrian7 Apr 27 '14 at 15:23
  • 3
    The actual rendering of an image may very well depend on factors which are out of your control (on the end user's computer), as a developer. I.e. the CPU load. So in the end, there's still the need of an event like 'painted'. – Andrei Oniga Jun 19 '14 at 15:17
  • For what exactly do you need to know when the painting has ended? Tell us more about your application. – Bergi Jul 02 '14 at 13:33

6 Answers6

31

I used requestAnimationFrame to do the trick. After image loaded it will be rendered during the next animation frame. So if you wait two animation frames your image will be rendered.

function rendered() {
    //Render complete
    alert("image rendered");
}

function startRender() {
    //Rendering start
    requestAnimationFrame(rendered);
}

function loaded()  {
    requestAnimationFrame(startRender);
}

See http://jsfiddle.net/4fg3K/1/

danronmoon
  • 3,814
  • 5
  • 34
  • 56
hovitya
  • 539
  • 5
  • 5
  • 6
    It doesn't work on any major browser (tested on Edge 15, Chrome 58 and FireFox 53): the alert box displays before the image has finished painting. – Gyum Fox Jun 08 '17 at 08:50
  • 5
    This actually worked for me when I put the code into the image `onload` callback. Tested in latest Chrome and FF, IE 11, Safari on iOS 10.3.3 and it worked everywhere (the alert appeared after the image has been displayed on the screen). The image I was testing was also quite big (around 2 MB desktop and 500 KB mobile). – margaretkru Aug 31 '17 at 09:32
  • Documentation: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame – jehon Oct 24 '22 at 07:43
6

I had the same problem. I have created the preloader page with progress bar. When the image is loaded, the white background preloader page disappears smoothly to opacity: 0, but the image is still not rendered.

I have finally used the setInterval, when the image is loaded (but not rendered). In the interval I check the naturalWidth and naturalHeight properties (Support: IE9+). Initialy it equals 0 and when the image is rendered it shows its current width and height.

const image = document.getElementById('image');

image.src = "https://cdn.photographylife.com/wp-content/uploads/2014/06/Nikon-D810-Image-Sample-1.jpg";

image.onload = function () {
  console.log('Loaded: ', Date.now());
  const interval = setInterval(() => {
    if (image.naturalWidth > 0 && image.naturalHeight > 0) {
      clearInterval(interval);
      rendered();
    }
  }, 20);
}

function rendered() {
  console.log('Rendered: ', Date.now());
}
img{
  width: 400px;
}
<img id="image"/>
Paweł
  • 4,238
  • 4
  • 21
  • 40
  • 3
    Using `naturalHeight` and `naturalWidth` to detect drawing is not reliable at all. In my tests with Firefox 52, Chromium 18, and Chromium 91, `naturalWidth` and `naturalHeight` are set >0 before the image's `onload` fires. Both Chromium versions raise `onload` before the image is initially drawn, and Firefox 52 raises `onload` after the initial draw. – TiberiumFusion Jul 14 '21 at 00:58
0

from mdn,

The MozAfterPaint event is triggered when content is repainted on the screen and provides information about what has been repainted. It is mainly used to investigate performance optimization

Hope you are doing this to gauge performance of the rendered image.

MIdhun Krishna
  • 1,739
  • 1
  • 13
  • 31
0

I'd like to propose a modified version of the answer that hovitya provided.

This is the behavior of their answer:

..r........P...................e.P..
  • r: execution of our function is requested
  • P: browser paints (typically happens 60x per second)
  • e: our function is executed

We can improve the behavior and target this result instead:

..r........P.e...................P..

i.e. our custom function is executed almost immediately after the first repaint

This is the code that makes this work:

function afterPaint() {
  // Render complete
  alert("image rendered");
}

// do some DOM manipulation here

requestAnimationFrame(() => {
  // wait until immediately before the next repaint,
  // then schedule a new microtask
  // - which will be executed immediately after the repaint
  queueMicrotask(afterPaint);
});

references

Marko Knöbl
  • 447
  • 2
  • 9
-1

Update: Do not use this approach - doesn't work in cases where the image dimensions are set to something else than the default

You could set the element's height to auto (with a fixed width) and with a timeout keep on checking of the element's dimensions match with the natural dimensions of the image. It's not the best solution but if you really need to do something after rendering and not after loading, it's a good option.

More or less that's how it could look:

//this is NOT a real code
function checkIfRendered(img, onRender) {
    var elRatio = img.width() / img.height();
    var natRatio = img.naturalWidth / img.naturalHeight;

    if (elRatio === natRatio)
        onRender();
    else {
        setTimeout(function() {
            checkIfRendered(imgEl, onRender)
        }, 20);
    }
}

img.onload(checkIfRendered(img, function() { alert('rendered!'); }));
phidias
  • 518
  • 4
  • 6
  • 1
    I believe the browser will return the height and width of the reserved image space within the page, not the size of the image as it is being painted. – Andrei Oniga Apr 28 '14 at 06:28
  • 1
    True you are right. This approach will NOT work correctly if there is some css on the image that sets its width or height. So do NOT use this apprach. I was going to delete this reply but I'll keep it here since it might save someone the trouble of trying something similar – phidias Sep 09 '14 at 13:05
  • @phidias feel free to delete a post that is incorrect :-) – jehon Oct 24 '22 at 07:45
-6

You need the onload event on the <img> tag. For example:

function loadImage () {
  alert("Image is loaded");
}
<img src="w3javascript.gif" onload="loadImage()" width="100" height="132">

source

If the image is the background of another element, load the image in the background (with a simple Ajax GET request) and when the result comes back, set the background after it has been loaded.

AlexStack
  • 16,766
  • 21
  • 72
  • 104