0

I'm fairly new to Javascript and I'm currently trying to display images at various sizes and positions. This means I have to load the images first, before I can access values like width and height. Now, here's where I'm facing problems.

I tried loading the images one after another, making sure another image would only be loaded when one image is completed.

const a = new Image();
const b = new Image();

const images = [
    a,
    b
];
const imageLinks = [
    'a.png',
    'b.png'
];

let loaded = 0;
for (let i = 0; i < images.length; i++) {
    images[i].onload = function() {
        console.log(images[i].width);
        console.log(images[i].height);
        loaded++;
    }
    images[i].src = imageLinks[i];
}

console.log(images[0].width);
console.log(images[0].height);

Obviously, this doesn't produce the correct result. The Logs after the for-loop are still 0 and are printed before the Logs inside the for-loop. That means, the program doesn't wait for the images to finish.

Next, I tried this:

let loaded = 0;
while (loaded < images.length) {
    images[loaded].onload = function() {
        console.log(images[loaded].width);
        console.log(images[loaded].height);
        loaded++;
    }
    images[loaded].src = imageLinks[loaded];
}

console.log(images[0].width);
console.log(images[0].height);

This does even worse. The page doesn't even load in the first place, it seems to be stuck forever in the while-loop. This makes sense because I reckon that everytime images[loaded].src is set, the loading process starts over, thus never making any progress.

Any solutions I've found include HTML-code, where images are loaded from HTML via document.querySelector(), which I cannot use, or are way too complicated for me to even try to wrap my head around as someone who's just starting out. I don't need a perfect solution, for now I just need something that works. It can't be that complicated, right? I just want to load a few images. I'm really stuck here.

  • Access the width and height in the `onload` callbacks only. Move the code that needs those values into the event handler. – Unmitigated Feb 25 '23 at 05:57
  • 1
    Does this answer your question? [How to return values from async functions using async-await from function?](https://stackoverflow.com/questions/49938266/how-to-return-values-from-async-functions-using-async-await-from-function) – Andy Ray Feb 25 '23 at 05:59
  • @Unmitigated The problem is that the program just keeps running and using 0 as its values for width and height. I need those values all over my code and I can't move everything into the event handler. – RobsonDaSheep Feb 25 '23 at 06:02
  • Unfortunately, that's what you need to do. Put *everything* in one big function. You can use promises and `Promise.all()` to wait for all the images to be loaded, then call that function. – Barmar Feb 25 '23 at 06:05
  • 1
    Async behavior in JS results in dozens of these exact types of questions asked on SO every day. Here's a recent exact duplicate of this one https://stackoverflow.com/questions/75563660/why-it-gives-a-broken-image-when-fetching – Andy Ray Feb 25 '23 at 06:26

4 Answers4

3

Here is another promise-based answer:

const images = [...document.querySelectorAll("div img")];

const proms=images.map(im=>new Promise(res=>
 im.onload=()=>res([im.width,im.height])
))

// list all image widths and heights _after_ the images have loaded:
Promise.all(proms).then(data=>{
  console.log("The images have loaded at last!\nHere are their dimensions (width,height):");
  console.log(data);
})

// Everything is set up here.
// Except: the images don't have their `src` attributes yet! 
// These are added now and the action will unfold:

images.forEach((im,i)=>im.src=`https://picsum.photos/id/${i+234}/200/100`);
<div><img><img></div>

A minimalist form of the above could look like this:

const images = [...document.querySelectorAll("div img")];

// list all image widths and heights _after_ the images have loaded:
Promise.all(images.map(im=>new Promise(resolve=>im.onload=resolve))).then(()=>{
  console.log("The images have loaded at last!\nHere are their dimensions (width,height):");
  console.log(images.map(im=>([im.width,im.height])));
})

// Now, trigger the action:
images.forEach((im,i)=>im.src=`https://picsum.photos/id/${i+234}/200/100`);
<div><img><img></div>
Carsten Massmann
  • 26,510
  • 2
  • 22
  • 43
  • Should be the the accepted answer, Promise.all is definitely the cleanest way to do this. – JBoss Jul 04 '23 at 22:57
0

Welcome, loaded++ is inside onload.

That's probably why it doesn't work. It's not worth uploading images in order. Async programming will have a better effect than blocking.

async function create() {
var i=0
var arr = [{data:'1'},{data:'2'},{data:'3'},{data:'4'},{data:'5'}]

while(i<arr.length) {
console.log('create', arr[i])
await read(arr[i])
i++
}}

async function read(val) {
console.log('read',val)
}

create()

or

async function* reader() {
  while (true) {
    const value = yield;
    console.log(value, '-----generator');
  }
}

const read = reader();
var i =0
var arr = [{data:'1'},{data:'2'},{data:'3'},{data:'4'},{data:'5'}]
read.next(0) //first value not return

async function create() {
while(i<arr.length) {
await read.next(arr[i]); 
console.log(arr[i], '-----loop');

i++
}
}
create()

Now do not come to that waiting it still performs in progress

Sorry for english i use translator

Pawells
  • 27
  • 8
0

To wait for all images loaded (or error) I'll use this:

window.addEventListener('DOMContentLoaded', (event) => {
    let imagesToLoad = [...document.images].filter(x => !x.complete);

    if (imagesToLoad == 0) {
        // All images loaded
    } else {
        imagesToLoad.forEach(imageToLoad => {
            imageToLoad.onload = imageToLoad.onerror = () => {
                if ([...document.images].every(x => x.complete)) {
                    // All images loaded
                }
            };
        });
    }
});

Another way, needs extra code:

window.addEventListener('DOMContentLoaded', (event) => {
    Promise.progress(Array.from(document.images).filter(img => !img.complete).map(img => new Promise(resolve => { img.addEventListener('load', resolve); img.addEventListener('error', resolve); })), (p) => {
        //showLoading(`Waiting for images on page to load.. (${p.loaded}/${p.total})`, 0, p.total, p.loaded);
    }).then(() => {
        // All images loaded
        //hideLoading();
    });

    // Promise progress helper
    Promise.progress = async function progress (iterable, onprogress) {
        const promises = Array.from(iterable).map(this.resolve, this);
        let resolved = 0;
        const progress = increment => this.resolve(onprogress(new ProgressEvent('progress', { total: promises.length, loaded: resolved += increment })));
        await this.resolve();
        await progress(0);
        return this.all(promises.map(promise => promise.finally(() => progress(1))));
    }
});
KoalaBear
  • 2,755
  • 2
  • 25
  • 29
0

A generic solution which checks for when all the images have loaded then calls function_after_image_is_loaded().

The problem with while loops is that it pauses the whole process, therefore the image loading process too. So, something like async, await, promises, must be involved. The following creates a promise which checks if all the images is loaded after around 10 milliseconds of its creation. For any given image element, if img.complete = true, then properties like width, clientWidth, naturalWidth, would be loaded. Then, if the images is not loaded yet, the promise will return a reject, which will be handled by catch(), and re-invoke the promise. The Math.random() is necessary to make each Promise a new instance, so that it does not reuse the results from previous Promise and skip the 10 milliseconds interval. (Without Math.random(), it can run thousands of times within a second, which will slow down the page.) And finally, when every image is loaded, it will return a resolve, handled by then(), and calls function_after_image_is_loaded(), and stop re-invoking itself.


function wait_for_image_load_then(function_to_be_called) {
    new Promise((resolve, reject) => {
        setTimeout(
            () => {
                // selects all images, adjust according to need
                images = document.querySelectorAll("img");
                // checks if all image is completey loaded
                // returns true only if every element in the array is true
                let all_complete = Array.from(images).every((img) => img.complete)
                if (all_complete) {
                    resolve('images load finish');
                }
                else {
                    reject('image loading');
                }
            },
            Math.random() + 10 // necessary to make each Promise a new instance 
        )
    }).then(
        // if resolve is returned in promise
        (res) => { console.log(res); function_to_be_called(); }
    ).catch(
        // if reject is returned in promise
        (reason) => { console.log(reason); wait_for_image_load_then(function_to_be_called); }
    );
}

function function_after_image_is_loaded() {
    images = document.querySelectorAll("img");
    Array.from(images).forEach((img) => console.log('width: ', img.width, '\theight: ', img.height));
}

wait_for_image_load_then(function_after_image_is_loaded);
antane
  • 81
  • 1
  • 3