jQuery Ajax等待所有图片加载完成

39

我正在尝试使用jQuery执行Ajax调用,它工作得很好。 我使用成功事件来显示数据。 但是似乎成功事件在外部HTML文件加载后立即触发。 如果有大图像,则它们会在显示后继续加载。 是否有一种方法可以在所有内容完全加载后显示内容? 以下是代码:

$('#divajax').html('<br><div style="text-align: center;"><img src="res/ajax-loader.gif"></div>');
$.ajax({
    cache: false,
    url: 'ajax/content.php',
    success: function(data) {
          $('#divajax').html(data);
    }
});
10个回答

49

@alex写的插件出了些问题,在某些情况下不能正常工作,我一直无法弄清楚原因。但他的代码激发了我想出了一个更轻量级的解决方案,这个方案对我来说有效。它使用jQuery promises。请注意,与@alex的插件不同,此插件并不尝试为元素上的背景图像进行计数,它只针对img元素。

// Fn to allow an event to fire after all images are loaded
$.fn.imagesLoaded = function () {

    // get all the images (excluding those with no src attribute)
    var $imgs = this.find('img[src!=""]');
    // if there's no images, just return an already resolved promise
    if (!$imgs.length) {return $.Deferred().resolve().promise();}

    // for each image, add a deferred object to the array which resolves when the image is loaded (or if loading fails)
    var dfds = [];  
    $imgs.each(function(){

        var dfd = $.Deferred();
        dfds.push(dfd);
        var img = new Image();
        img.onload = function(){dfd.resolve();}
        img.onerror = function(){dfd.resolve();}
        img.src = this.src;

    });

    // return a master promise object which will resolve when all the deferred objects have resolved
    // IE - when all the images are loaded
    return $.when.apply($,dfds);

}

然后你可以像这样使用它:

$.ajax({
    cache: false,
    url: 'ajax/content.php',
    success: function(data) {
        $('#divajax').html(data).imagesLoaded().then(function(){
            // do stuff after images are loaded here
        });
    }
});

希望这对某些人有所帮助。

请注意,如果其中一张图像出现错误(例如由于URL错误),承诺仍将被解决并且错误将被忽略。 这可能是您想要的,但根据您的情况,如果图像加载失败,您可能希望中止正在进行的任何操作。 在这种情况下,您可以将onerror行替换为以下内容:

img.onerror = function(){dfd.reject();}

并像这样捕获错误:

$('#divajax').html(data).imagesLoaded().done(function(){
    // do stuff after all images are loaded successfully here
}).fail(function(){
    // do stuff if any one of the images fails to load
});

这看起来可能会导致内存泄漏。每次进行ajax调用时,都会在内存中创建页面上每个图像的副本,并将事件附加到每个新图像对象上,在imagesLoaded函数执行完成后仍将图像对象保留在内存中。一旦所有重复的图像对象加载完毕,它们何时从内存中删除?是在执行onload事件回调之后直接删除吗? - Mark Entingh
3
@MarkEntingh - 你对内存泄漏的看法可能是正确的 - 我不确定。一旦 Promise 被解决,闭包中的 Image 对象是否还有任何引用?我认为没有,因此 JS 应该垃圾回收它。如果你担心这个问题,可以在函数外部定义一个 Image 对象数组,并在 'then' 函数中明确删除它们。请注意 - 你说过 '页面上每个图片的副本' - 这只有在将其应用于整个文档 - $(document).imagesLoaded() - 的情况下才是这样。通常情况下,它只会是一个很小的子集或单个图像。 - Daniel Howard
1
我建议也处理onerror事件,否则回调可能永远不会被调用。 - Joshua Walsh
2
很好的建议 @YM_Industries - 我已经更新了代码以包含您的建议。 - Daniel Howard
CSS背景图片怎么样? - Krii
1
@Kril 如果你需要包括背景图片,那么你必须找到一种方法来搜索“this”元素使用的所有背景图片,并将它们添加到 $imgs 数组的开头。 - Daniel Howard

18
你可以使用我的jQuery插件,waitForImages...
$.ajax({
    cache: false,
    url: 'ajax/content.php',
    success: function(data) {

         $('#divajax').html(data).hide().waitForImages(function() {
             $(this).show();
         });

    }
});

这将把内容加载到您的元素中,隐藏它,然后在子级img元素加载后重新显示它。


1
@alex,我也可以用这个来进行追加吗?基本上我正在做无限滚动,并将元素附加到现有容器中,以及其他图像。 - kobe
@alex,非常感谢你的插件,我将我的容器div添加进去了,它正常工作了。 - kobe

10

您可以绑定一些内容到载入事件,以了解它们何时完成:

$('<img>').bind('load', function() {
    $(this).appendTo('body');
});

或者你可以使用这个插件


1
哦,天啊,你不知道你帮了我多少忙,我已经找了好几个小时的解决方案了...非常感谢你:D - Linas
5
现在似乎推荐使用 .on() 而不是 .bind()。 - UpTheCreek

5

这会在每个图像单独加载时触发:

$('img').on('load', function() {
      // do something
});

我使用了:

var loaded = 0;
$('img').on('load', function() {
   loaded++;
   if(loaded == $('img').length){
      // do something
   }
});

2
您可以使用与JavaScript和jQuery一起使用的imagesloaded插件,尝试在加载的内容中使用ajax success回调函数,并在imagesloaded回调函数触发之前隐藏所有内容。请参阅以下示例代码。
$.ajax({
    cache: false,
    url: 'ajax/content.php',
    success: function(data) {
        var $divajax = $('#divajax').html(data).hide();
        $divajax.imagesLoaded(function() {
             $divajax.show();
         });

    }
});

0
$('#divajax').html('<br><div style="text-align: center;"><img src="res/ajax-loader.gif"></div>').load(function() {
$.ajax({
    cache: false,
    url: 'ajax/content.php',
    success: function(data) {
          $('#divajax').html(data);
    }
});
});

0

研究使用jQuery的.load()方法

该方法的描述如下:

当元素及其所有子元素完全加载时,将向该元素发送load事件。此事件可以发送到与URL相关联的任何元素:图像、脚本、框架、iframe和窗口对象。


事实是,我无法通过正确设置缓存选项来关闭异步调用,对吧? - robs

0

试试这段代码,它可以正常工作。

$.ajax({
    url: "../api/listings",
    type: 'GET',
    success: function (html) {
        $('#container').append(html);
        $('#container img').on('load', function(){console.log('images loaded')});
    };
});

0

我在使用Daniel的插件时遇到了一个小错误,当没有找到任何图片时。

以防其他人遇到相同的问题:

更改为:

// if there's no images, just return an already resolved promise
if (!$imgs.length) {return $.Deferred.resolve().promise();}

至:

// if there's no images, just return an already resolved promise
    if (!$imgs.length) {
        var dfd = $.Deferred();
        return dfd.resolve().promise();
    }

-6

我认为最好使用这个

$(window).ready(function(){ //load your ajax})

6
由于 $(document).ready()$(window).load() 在 AJAX 请求后不会触发,所以这种方法行不通。 - Andreyco

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