admin管理员组

文章数量:1353527

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 pleted.

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 plicated 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 plicated, right? I just want to load a few images. I'm really stuck here.

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 pleted.

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 plicated 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 plicated, right? I just want to load a few images. I'm really stuck here.

Share Improve this question edited Feb 25, 2023 at 5:56 RobsonDaSheep asked Feb 25, 2023 at 5:53 RobsonDaSheepRobsonDaSheep 111 silver badge4 bronze badges 5
  • Access the width and height in the onload callbacks only. Move the code that needs those values into the event handler. – Unmitigated Commented Feb 25, 2023 at 5:57
  • 1 Does this answer your question? How to return values from async functions using async-await from function? – Andy Ray Commented Feb 25, 2023 at 5: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 Commented Feb 25, 2023 at 6: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 Commented Feb 25, 2023 at 6: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 stackoverflow./questions/75563660/… – Andy Ray Commented Feb 25, 2023 at 6:26
Add a ment  | 

4 Answers 4

Reset to default 9

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>

Wele, 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 e to that waiting it still performs in progress

Sorry for english i use translator

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

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

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

Another way, needs extra code:

window.addEventListener('DOMContentLoaded', (event) => {
    Promise.progress(Array.from(document.images).filter(img => !img.plete).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))));
    }
});

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.plete = 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 pletey loaded
                // returns true only if every element in the array is true
                let all_plete = Array.from(images).every((img) => img.plete)
                if (all_plete) {
                    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);

本文标签: How to make Javascript wait for all images to load before proceedingStack Overflow