Want a one-liner?
Promise.all(Array.from(document.images).filter(img => !img.complete).map(img => new Promise(resolve => { img.onload = img.onerror = resolve; }))).then(() => {
console.log('images finished loading');
});
Pretty backwards-compatible, works even in Firefox 52 and Chrome 49 (Windows XP era). Not in IE11, though.
Replace document.images
with e.g. document.querySelectorAll(...)
if you want to narrow the image list.
It uses onload
and onerror
for brevity. This might conflict with other code on the page if these handlers of the img
elements are also set elsewhere (unlikely, but anyway). If you're not sure that your page doesn't use them and want to be safe, replace the part img.onload = img.onerror = resolve;
with a lengthier one: img.addEventListener('load', resolve); img.addEventListener('error', resolve);
.
It also doesn't test whether all images have loaded successfully (that there are no broken images). If you need this, here's some more advanced code:
Promise.all(Array.from(document.images).map(img => {
if (img.complete)
return Promise.resolve(img.naturalHeight !== 0);
return new Promise(resolve => {
img.addEventListener('load', () => resolve(true));
img.addEventListener('error', () => resolve(false));
});
})).then(results => {
if (results.every(res => res))
console.log('all images loaded successfully');
else
console.log('some images failed to load, all finished loading');
});
It waits until all images are either loaded or failed to load.
If you want to fail early, with the first broken image:
Promise.all(Array.from(document.images).map(img => {
if (img.complete)
if (img.naturalHeight !== 0)
return Promise.resolve();
else
return Promise.reject(img);
return new Promise((resolve, reject) => {
img.addEventListener('load', resolve);
img.addEventListener('error', () => reject(img));
});
})).then(() => {
console.log('all images loaded successfully');
}, badImg => {
console.log('some image failed to load, others may still be loading');
console.log('first broken image:', badImg);
});
Two latest code blocks use naturalHeight
to detect broken images among the already loaded ones. This method generally works, but has some drawbacks: it is said to not work when the image URL is set via CSS content
property and when the image is an SVG that doesn't have its dimensions specified. If this is the case, you'll have to refactor your code so that you set up the event handlers before the images begin to load. This can be done by specifying onload
and onerror
right in the HTML or by creating the img
elements in the JavaScript. Another way would be to set src
as data-src
in the HTML and perform img.src = img.dataset.src
after attaching the handlers.