JavaScript中等待图像加载完成

6

我正在使用JavaScript加载图片。就像这样:

images[0]=new Image();
images[0].onload=function(){loaded++;console.log(loaded)};
images[0].src="assets/img/image.png";

当我查看日志时,我发现所有的图片都已经成功加载,因为"loaded"变量的值随着每个已加载的图片而增加。
但是我想要在此数量达到最大值之前停止任何进一步的操作,所以在设置完图片后,我会放置一个while循环。
while(loaded<11){
    document.getElementById("test").innerHTML="Loading "+loaded+"/11";
    console.log(loaded);
}
//Some code here which should only run after everything has been loaded
//In other words: when the statement in the while cycle becomes false

然而,我的浏览器崩溃了,因为while循环似乎陷入了死循环。当我检查日志时,我看到“0”被写了1000次,之后是从1到11的数字(这意味着图像实际上已经加载完毕,但while不关心它,比它发生得更快地崩溃)。
我认为我在这里尝试使用的方法并不是解决这个问题的正确方法。
如何将一切暂停,直到站点所需的所有资产都已加载?

当条件为真时,while循环会尽可能快地循环。 - vlatkokaplan
3个回答

21
使用Promise和async函数,有一种很好的方法来等待所有图像加载完成(无回调函数,无需计算已加载的图像数):
async function loadImages(imageUrlArray) {
    const promiseArray = []; // create an array for promises
    const imageArray = []; // array for the images

    for (let imageUrl of imageUrlArray) {

        promiseArray.push(new Promise(resolve => {

            const img = new Image();
            // if you don't need to do anything when the image loads,
            // then you can just write img.onload = resolve;

            img.onload = function() {
                // do stuff with the image if necessary

                // resolve the promise, indicating that the image has been loaded
                resolve();
            };

            img.src = imageUrl;
            imageArray.push(img);
        }));
    }

    await Promise.all(promiseArray); // wait for all the images to be loaded
    console.log("all images loaded");
    return imageArray;
}

或者你可以等待单个图片加载完成:

async function loadImage(imageUrl) {
    let img;
    const imageLoadPromise = new Promise(resolve => {
        img = new Image();
        img.onload = resolve;
        img.src = imageUrl;
    });

    await imageLoadPromise;
    console.log("image loaded");
    return img;
}

你可以像这样使用它(使用Promise链):

您可以像以下方式使用它(使用Promise链):

loadImages(myImages).then(images => {
    // the loaded images are in the images array
})

或者在异步函数内部:

const images = await loadImages(myImages);

1
终于有人使用 Promise 而不是 jQuery 或计数器了,我一直在寻找这样的东西!谢谢你! - lilgallon

8

个人而言,我不喜欢使用while()... 我认为最简单的方法是使用事件监听器。

var img = new Image;
img.addEventListener("load", function () {

//Img loaded

});
img.src= e.target.result;

非常感谢!我已经为每个图像添加了一个监听器,并通过它们检查变量是否达到所需的数量。因此,最后开始的事件也会启动脚本的其余部分。只是出于好奇:我在图像定义之后或之前添加监听器有关系吗?我已经在之前添加了它,但我想知道这样做会有什么不同。 - Letokteren
我认为我的例子很糟糕。通常在定位源之前应该添加事件监听器。其背后的逻辑是在执行者之前先有观察者。 - Said Hasanein

3

Javascript是单线程的,这意味着如果您添加了一个事件监听器,该监听器将不会在当前执行完成之前运行。因此,如果您开始一个依赖于事件结束的循环,则循环永远不会结束,因为该事件永远不会触发,因为当前执行正在阻止它运行。此外,事件是以异步方式放置在调用堆栈上,因此,如果您的执行速度比事件触发速度要慢(即将调用放置在调用堆栈上),则还会面临页面崩溃的风险。当使用setInterval时,很容易犯这种常见错误,因为间隔时间小于代码执行所需的时间。绝不要使用setInterval。

请记住,Javascript无法同时执行两个任务。

处理资源监视加载的最佳方法是使用setTimeout。

var allLoaded = false;
var imgCount = 0;  // this counts the loaded images
// list of images to load
const imageURLS =["a.jpg","b.jpg","c.jpg","d.jpg","e.jpg"];
// array of images
var images = [];

const onImageLoad = function(){ imgCount += 1; } // onload event
// loads an image an puts it on the image array
const loadImage = function(url){
    images.push(new Image());
    images[images.length-1].src = url
    images[images.length-1].onload = onImageLoad;
}
const waitForLoaded = function(){
    if(imgCount === images.length){
        allLoaded = true;   // flag that the image have loaded
    }else{
        // display  the progress here
        ...

        setTimeout(waitForLoaded,100); // try again in 100ms
    }
}

// create the images and set the URLS
imageURLS.forEach(loadImage);

setTimeout(waitForLoaded,100);  // monitor the image loading

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接