96

I'm making an Ajax call which returns me some info including an image path.

I prepare all this information in my HTML which will be displayed as a kind of popup. I just toggle the visibility of by popup div from hidden to visible.

To set the position of my popup div, I have to calculate depending on the height of the image. So, I have to wait for the image to load to know its dimension before setting position and switching visibility to visible.

I tried tricks with recursion, setTimeout, complete img property, while loop... without success.

So, how can I do this? Maybe I should return dimensions in my Ajax call.

1
  • why doesn't it work with the Image.complete property? Commented Feb 26, 2010 at 14:14

8 Answers 8

163
var img = new Image();
img.onload = function() { alert("Height: " + this.height); }
img.src = "http://path/to/image.jpg";

Note that it's important to do it in the order above: First attach the handler, then set the src. If you do it the other way around, and the image is in cache, you may miss the event. JavaScript is run on a single thread in browsers (unless you're using web workers), but browsers are not single-threaded. It's perfectly valid for the browser to see the src, identify the resource is available, load it, trigger the event, look at the element to see if it has any handlers that need to be queued for callback, not see any, and complete the event processing, all between the src line and the line attaching the handler. (The callbacks wouldn't happen between the lines if they were registered, they'd wait in the queue, but if there aren't any, the event isn't required to wait.)

9
  • Grat! BTW: i'm quite ignorant on the AJAX subject, but just in case an image got with AJAX is cached in the same way of an image written dierctly into HTML code, you could eventually add this in order to avoid browser's image caching (obviously only if you need it): img.src="path/to/image.jpg"+"?refresh="+new Date().getTime(); Commented Feb 26, 2010 at 15:21
  • 2
    @Marco Or set cache: false on the XHR :) Although I don't think it's relevant here because the AJAX call is returning a URL to the image, not the image itself. But yes I suppose he could still use a random query string value to make sure no user-agent caching occurs. Commented Feb 26, 2010 at 17:51
  • 1
    Can you add an event listener as well, or is replacing onload the way?
    – IQAndreas
    Commented Apr 9, 2014 at 0:13
  • 3
    @IQAndreas: You could use img.addEventListener("load", ...), yes. (Or img.attachEvent("onload", ...) on old IE). Commented Aug 17, 2015 at 10:14
  • 1
    Does it matter if I change the order of second and third line? Commented Sep 2, 2019 at 11:08
19

Now .decode() is the best approach, bringing native promise functionality to image loading:

const img = new Image();
img.src = "https://picsum.photos/85/150";
img
  .decode()
  .then(() => {
    document.body.appendChild(img);
  })
  .catch(encodingError => console.error(encodingError));

Extending the above code, Promise.all can be used to load a batch of images in parallel (Promise.allSettled is useful if you want to keep going even if some images don't load).

const loadImage = async src => {
  const img = new Image();
  img.src = src;
  await img.decode();
  return img;
};

const imageUrls = [
  "https://picsum.photos/85/150",
  "https://picsum.photos/85/130",
  "https://picsum.photos/85/110",
];
Promise.all(imageUrls.map(loadImage))
  .then(images => {
    const canvas = document.createElement("canvas");
    document.body.appendChild(canvas);
    const ctx = canvas.getContext("2d");
    images.forEach((image, i) =>
      ctx.drawImage(image, i * 90, 0, image.width, image.height)
    );
  })
  .catch(err => console.error(err));


For environments that don't support .decode(), you can promisify the onload and onerror callbacks.

const loadImage = src =>
  new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });

loadImage("https://picsum.photos/85/150").then(image => 
  console.log(image, `\nloaded? ${image.complete}`)
);

With the above function, Promise.all or Promise.allSettled can be used to load a batch of images in parallel.

const loadImage = src =>
  new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });

const imageUrls = [
  "https://picsum.photos/85/150",
  "https://picsum.photos/85/130",
  "https://picsum.photos/85/110",
];
Promise.all(imageUrls.map(loadImage))
  .then(images => {
    const canvas = document.createElement("canvas");
    document.body.appendChild(canvas);
    const ctx = canvas.getContext("2d");
    images.forEach((image, i) =>
      ctx.drawImage(image, i * 90, 0, image.width, image.height)
    );
  })
  .catch(err => console.error(err));

Improvements to loadImage might include additional parameters (such as crossOrigin, for example). A settings object or providing an Image as a parameter is another approach to generalize the function (setting src fires the request, so that should go last after handlers have been added).

0
14

If you use jQuery, you can use its load event.

Have a look at the example:

$('img.userIcon').load(function(){
    if($(this).height() > 100) {
        $(this).addClass('bigImg');
    }
});
2
  • 28
    -1: no mention of jQuery in the question, so an answer like this can be quite confusing.
    – Andy E
    Commented Feb 26, 2010 at 14:39
  • 3
    Please also note the following caveats with the load method when used with images (taken from api.jquery.com/load-event): > It doesn't work consistently nor reliably cross-browser > It doesn't fire correctly in WebKit if the image src is set to the same src as before > It doesn't correctly bubble up the DOM tree > Can cease to fire for images that already live in the browser's cache Commented Dec 11, 2013 at 13:15
9

Function to wait for image, that is already in HTML.

Promise example. Does not rejects just waits for load or fail:

function waitForImage(imgElem) {
    return new Promise(res => {
        if (imgElem.complete) {
            return res();
        }
        imgElem.onload = () => res();
        imgElem.onerror = () => res();
    });
}



// test
(async () => {
  const img = document.querySelector('img');
  // to not to cache in the test, set src dynamically
  img.src = 'https://www.gravatar.com/avatar/71094909be9f40bd576b1974a74966aa?s=48&d=identicon&r=PG&f=1';

  console.log('height before', img.naturalHeight); // 0
  await waitForImage(img);
  console.log('height after', img.naturalHeight); // 48
})();
<img src=""/>

Version with reject, if you need to know, if its failed:

function waitForImage(imgElem) {
    return new Promise((res, rej) => {
        if (imgElem.complete) {
            return res();
        }
        imgElem.onload = () => res();
        imgElem.onerror = () => rej(imgElem);
    });
}



// test
(async () => {
  const img = document.querySelector('#rightone');
  // to not to cache in the test, set src dynamically
  img.src = 'https://www.gravatar.com/avatar/71094909be9f40bd576b1974a74966aa?s=48&d=identicon&r=PG&f=1';

  console.log('height before', img.naturalHeight); // 0
  await waitForImage(img); // success
  console.log('height after', img.naturalHeight); // 48

  try {
    const failImg = document.querySelector('#wrongone');
    failImg.src = 'https://wrongimage';
    await waitForImage(failImg); // rejects after some time
  } catch(e) {
    console.log('failed to load image', e)
  }


})();
<img id="rightone" src="">
<img id="wrongone" src="">

0
3

just wrap your image onload in a function with a promise and then call it with await.

async drawImg(ctx, image){

    return new Promise(resolve => {

          image.onload = function () {
                ctx.drawImage(image, 10, 10, 200, 180);
                resolve('resolved');
             }

    });

  }

it should work just fine

1
  • 3
    The async keyword here is unnecessary--returning a promise is enough for the caller to await it. It's also a good idea to promisify the onerror handler with reject() so the caller can catch the error if the load fails. Last suggestion: you could pass the image object itself to resolve(this) so that the caller can draw the image or do what they want with it.
    – ggorlen
    Commented Feb 13, 2021 at 0:31
3

How about the window load event?

window.addEventListener('load', (event) => {
  //do stuff with images
});

or

window.onload = (event) => {
  //do stuff with images
};

https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event

This worked for me where I needed the browser to calculate the size of the images before running a layout script.

1
  • 3
    This doesn't quite seem to answer the question, as there are many times that an image can be loaded after the page itself loads, including this example. The onload event on window does wait for images to be loaded, but only for the initial page load. In the context of this question, the onload event should be tied directly to the image element that is being loaded instead.
    – Joseph
    Commented Mar 30, 2021 at 2:08
2

UPDATE 2024

Just adding to the already given answers since this still ranks high on search engines. There is actually a builtin method now for exactly this use case. It's called decode and is just as simple as this:

const img = new Image();
img.src = "img/logo.png";
await img.decode();
// Do whatever you want with the loaded image

It will throw an error if the image fails to load, and you can catch it just like any other async function with .catch() or a try-catch block.

-2

I had a slow CAPTCHA (1st_image) image loading on my page and I wanted to display another image (2nd_image) only AFTER the CAPTCHA image (1st_image) is loaded. So I had to wait for the CAPTCHA (1st_image) image to load first.

Here is the solution that works perfectly fine to wait for an image to load first and then load another image (don't forget to display a "please wait!" image while they are waiting for other image to load):

<script>
    function checkImageLoad() {
        if (document.getElementById("1st_image").complete == true) {
            console.log("1st_image Loaded!");
        }
        document.getElementById("2nd_image").src = "http://example.org/2nd_image.png";
    }
</script>
<body onload="checkImageLoad();">
<img id="1st_image" src="http://example.org/1st_image.png">
<img id="2nd_image" src="http://example.org/loading_please_wait.gif">

Either the Internet speed is slow or fast, and the browser will wait for the whole page to load with all the images (even if they are linked externally) and then execute the function, so the second image is displayed only after the first image loads fine.

Advanced note: You can use a web page URL in the "src" of the image to load a web page first and then show the image. The purpose is to load the cookie from the external webpage which might effect the second image displayed (like CAPTCHA).

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.