使用jQuery预加载图片

699

我正在寻找一种快速简单的方法来使用JavaScript预加载图片。如果重要的话,我正在使用jQuery。

我在这里看到了这个 (http://nettuts.com...):

function complexLoad(config, fileNames) {
  for (var x = 0; x < fileNames.length; x++) {
    $("<img>").attr({
      id: fileNames[x],
      src: config.imgDir + fileNames[x] + config.imgFormat,
      title: "The " + fileNames[x] + " nebula"
    }).appendTo("#" + config.imgContainer).css({ display: "none" });
  }
};

但是,它看起来有点过头了,不符合我的要求!

我知道有一些jQuery插件可以做到这一点,但它们都有点大(在大小方面);我只需要一个快速、简单和短小的预加载图像的方式!


11
$.each(arguments,function(){(new Image).src=this}); 意思是遍历arguments中的每个元素,对于每个元素都创建一个Image对象,并将该元素作为Image对象的src属性值。 - David Hellsing
20个回答

977

快速而简单:

function preload(arrayOfImages) {
    $(arrayOfImages).each(function(){
        $('<img/>')[0].src = this;
        // Alternatively you could use:
        // (new Image()).src = this;
    });
}

// Usage:

preload([
    'img/imageName.jpg',
    'img/anotherOne.jpg',
    'img/blahblahblah.jpg'
]);

或者,如果你想使用jQuery插件:

$.fn.preload = function() {
    this.each(function(){
        $('<img/>')[0].src = this;
    });
}

// Usage:

$(['img1.jpg','img2.jpg','img3.jpg']).preload();

26
为确保浏览器缓存图像元素,难道不需要将其插入到 DOM 中吗? - JoshNaro
10
我认为 $('<img />') 只是在内存中创建了一个图像元素(请参见链接)。看起来 '$('<img src="' + this + '" />')' 会在隐藏的 DIV 中创建元素,因为它更加 "复杂"。然而,我不认为大多数浏览器需要这样做。 - JoshNaro
104
写jQuery插件的方式有些奇怪。为什么不用 $.preload(['img1.jpg', 'img2.jpg']) - alex
12
请确保在 $(window).load(function(){/*preload here*/}); 中调用此函数,因为这样文档中的所有图像都会首先加载,很可能它们是首先需要的。 - Jasper Kennis
12
在设置src之前先设置load事件可能会更安全一些,以防图片在设置load事件之前就已经加载完成了。可以使用以下代码:$('<img/>').load(function() { /* it's loaded! */ }).attr('src', this); - sparebytes
显示剩余20条评论

106

这是第一个回答的修改版本,它实际上将图片加载到DOM中并默认隐藏。

function preload(arrayOfImages) {
    $(arrayOfImages).each(function () {
        $('<img />').attr('src',this).appendTo('body').css('display','none');
    });
}

39
hide()css('display','none')更加简洁。 - alex
6
将它们插入到DOM中的好处是什么? - alex
我也想知道将它们插入DOM的优势是什么。 - jedmao
11
根据我的经验,将图像预加载到DOM中可以让浏览器知道其存在并且可以被正确缓存。否则,该图像只存在于内存中,这仅适用于单页应用程序。 - Dennis Rongo
1
Dennis Rongo。将图像附加到DOM上可以解决Chrome浏览器的随机重新加载问题。谢谢! - tmorell
有没有一种方法可以延迟加载图片?我目前遇到的问题是,在我的HTML中,所有这些大图像在加载那些更重要的小图像之前被加载了(那些我通过JavaScript分配的)。 - John

55

使用JavaScript Image对象

该函数允许您在加载所有图片后触发回调。但是,请注意,如果至少有一个资源未加载,则它将永远不会触发回调。可以通过实现onerror回调并递增loaded值或处理错误轻松解决此问题。

var preloadPictures = function(pictureUrls, callback) {
    var i,
        j,
        loaded = 0;

    for (i = 0, j = pictureUrls.length; i < j; i++) {
        (function (img, src) {
            img.onload = function () {                               
                if (++loaded == pictureUrls.length && callback) {
                    callback();
                }
            };

            // Use the following callback methods to debug
            // in case of an unexpected behavior.
            img.onerror = function () {};
            img.onabort = function () {};

            img.src = src;
        } (new Image(), pictureUrls[i]));
    }
};

preloadPictures(['http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar'], function(){
    console.log('a');
});

preloadPictures(['http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar', 'http://foo/picture.bar'], function(){
    console.log('b');
});

4
做正确的事情,使用JavaScript图像对象。你有没有观察到这些答案中有人做了错误的事情? - alex
3
如果目标是预加载(这暗示了性能顺序),那么 @alex 可能会同意。我更倾向于看到一个原始的 JS 选项,而不是依赖 jQuery 的选项。 - Charlie Schliesser
你为什么要在匿名函数中包装 for 循环体? - Greg0ry
@Greg0ry 这是一个用于创建新作用域的IIFE。 - Gajus
从性能角度来看,没有可衡量的差异。但是为了可读性,您可以使用预定义函数(并且应该这样做)。 - Gajus
显示剩余13条评论

35

JP,经过检查你的解决方案后,在使用Firefox时仍然遇到了问题,即在加载页面之前无法预加载图像。我通过在服务器端脚本中添加一些sleep(5)来发现这个问题。我根据你的解决方案实现了以下解决方法,似乎解决了这个问题。

基本上,我在你的jQuery预加载插件中添加了一个回调函数,以便在所有图像正确加载后调用它。

// Helper function, used below.
// Usage: ['img1.jpg','img2.jpg'].remove('img1.jpg');
Array.prototype.remove = function(element) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == element) { this.splice(i,1); }
  }
};

// Usage: $(['img1.jpg','img2.jpg']).preloadImages(function(){ ... });
// Callback function gets called after all images are preloaded
$.fn.preloadImages = function(callback) {
  checklist = this.toArray();
  this.each(function() {
    $('<img>').attr({ src: this }).load(function() {
      checklist.remove($(this).attr('src'));
      if (checklist.length == 0) { callback(); }
    });
  });
};

出于兴趣,在我的环境中,我使用它如下:

$.post('/submit_stuff', { id: 123 }, function(response) {
  $([response.imgsrc1, response.imgsrc2]).preloadImages(function(){
    // Update page with response data
  });
});

希望这篇文章能够帮助到从Google搜索进来的人(就像我一样)寻找在Ajax调用中预加载图片的解决方案。


2
对于那些不想搞乱Array.prototype的人,可以这样做:checklist = this.length 在onload函数中:checklist-- 然后:if (checklist == 0) callback(); - MiniGod
3
在JavaScript中,虽然一切都会很好,但是向不是你所拥有的对象添加新方法或删除现有方法是最糟糕的做法之一。 - Rihards
代码很简洁,但如果其中一张图片损坏或无法加载,该代码将永远不会触发回调。 - SammyK
.attr({ src: this }).load(function() {...}) 应该改为 .load(function() {...}).attr({ src: this }),否则会出现缓存问题。 - Kevin B

25

这一行 jQuery 代码可以创建(并加载)一个 DOM 元素 img,但不会显示它:

$('<img src="img/1.jpg"/>');

4
@huzzah - 你最好使用精灵图,这样可以减少http请求的次数。 :) - Alex K

23
$.fn.preload = function (callback) {
  var length = this.length;
  var iterator = 0;

  return this.each(function () {
    var self = this;
    var tmp = new Image();

    if (callback) tmp.onload = function () {
      callback.call(self, 100 * ++iterator / length, iterator === length);
    };

    tmp.src = this.src;
  });
};

使用方法非常简单:

$('img').preload(function(perc, done) {
  console.log(this, perc, done);
});

http://jsfiddle.net/yckart/ACbTK/


这是一个非常好的答案,我认为它应该成为最高投票的答案。 - SleepyCal
1
一些解释性的话会更好。 - robsch
好的回答,但我建议使用 https://picsum.photos 而不是 lorempixel.com。 - Aleksandar

19

我有一个小插件可以处理这个问题。

它叫做 waitForImages,它可以处理 img 元素或者在 CSS 中引用的任何带有图片的元素,例如: div { background: url(img.png) }

如果您只是想加载 所有 图片,包括 CSS 中引用的图片,那么下面是如何实现的 :)

$('body').waitForImages({
    waitForAll: true,
    finished: function() {
       // All images have loaded.
    }  
});

这个插件会导致尚未出现在页面上的图像被加载,还是只会附加事件到已经要被加载的图像? - Dave Cameron
@DaveCameron 它不会考虑图像是否可见。你可以轻松地分叉它并进行更改 - 只需将 :visible 添加到自定义选择器即可。 - alex
这个插件看起来非常有趣。然而,jQuery文档强调,当应用于图像时,“load”事件“在跨浏览器方面不一致且不可靠”。那么这个插件是如何解决这个问题的呢? - EleventyOne
@EleventyOne请查看源代码以了解自定义选择器。 - alex
@alex 这个插件如何加载在元素的:hover状态下在CSS中设置的图片?它对我不起作用。 - Philipp Hofmann
@muffls 文档从未声称要做到这一点。 - alex

13

这个 jQuery imageLoader 插件大小只有 1.39kb。

使用方法:

$({}).imageLoader({
    images: [src1,src2,src3...],
    allcomplete:function(e,ui){
        //images are ready here
        //your code - site.fadeIn() or something like that
    }
});

还有其他选项,例如您想同步或异步加载图像以及为每个单独的图像提供完整事件。


1
@Ian 因为文档在回调被触发时已经准备就绪。$(document).ready用于确保DOM已加载。当涉及到回调时,这意味着所有的图像都已加载,这意味着您的DOM已经加载完毕,因此不需要在回调中使用document.ready。 - Aamir Afridi
1
@AamirAfridi 不能保证回调函数中的文档已准备就绪...你是从哪里推断出来的?插件页面上没有任何内容表明allcomplete回调在DOM准备就绪后执行。图片完成加载的可能性很大是在DOM准备就绪之后,但没有理由去假设。我不知道为什么你认为回调函数是神奇的并且在DOM准备就绪后执行...你能解释一下你是从哪里得到这个想法的吗?仅仅因为图片加载完毕并不意味着DOM已经准备就绪。 - Ian
@AamirAfridi 而且插件的文档甚至说:NB要将其用作图像预加载器,只需将您的$(document).ready(function(){});放入“allcomplete”事件中:>简单!...它直接推荐了这个答案所展示的内容。 - Ian
@Ian,将你的插件调用放在$(document).ready内总是明智的,这样可以避免所有这些问题。 - Aamir Afridi
5
@AamirAfridi 在这种情况下没有任何意义。该插件是一个预加载程序,您希望尽快执行它。有很多不依赖于DOM的事情,您希望尽快触发它们,然后在DOM准备好时做一些事情。 - Ian
这个答案中的链接已经失效了。 - Qqwy

13

您可以使用CSS的display:none;规则在HTML中某个位置加载图像,然后使用JS或jQuery在需要时显示它们。

不要使用JS或jQuery函数进行预加载,只需使用CSS规则即可,这比执行许多行JS更简单。

示例:Html

<img src="someimg.png" class="hide" alt=""/>

CSS:

.hide{
display:none;
}

jQuery:

//if want to show img 
$('.hide').show();
//if want to hide
$('.hide').hide();

使用 jQuery/JavaScript 预加载图像并不好,因为图像需要几毫秒才能在页面中加载,而您只有几毫秒来解析和执行脚本,特别是当它们是大型图像时,因此在 hml 中隐藏它们更好,也会提高性能,因为图像真正预加载而且根本不可见,直到您显示它们!


2
这种方法的更多信息可以在这里找到:http://perishablepress.com/a-way-to-preload-images-without-javascript-that-is-so-much-better/ - gdelfino
3
但你需要知道这种技术有一个主要缺点:直到所有图片加载完毕,你的页面才能完全加载。根据要预加载的图片数量和大小,这可能需要一些时间。更糟糕的是,如果<img>标签没有指定高度和宽度,某些浏览器可能会等待图像被获取后再呈现页面的其余部分。 - user246645
@Alex,无论如何你都需要加载图片,你可以选择使用HTML加载并避免任何闪烁和半加载的图像,或者如果你想获得更快的速度,可以选择不稳定的解决方案。 - itsme
我真的觉得用JavaScript创建HTML标签完全不可读,这只是我的个人意见。 - itsme
1
我需要一个非常快速的解决方案来完成一个非常复杂的项目,而这正是它。 - Germstorm
出于用户体验的考虑,我使用了许多隐藏的图像。因此,在这种情况下,这个解决方案不适合! - Diego Favero

9
使用jQuery无需插件快速预加载图片并获得回调函数的方法是同时创建多个img标签,并计算响应次数,例如:
function preload(files, cb) {
    var len = files.length;
    $(files.map(function(f) {
        return '<img src="'+f+'" />';
    }).join('')).load(function () {
        if(--len===0) {
            cb();
        }
    });
}

preload(["one.jpg", "two.png", "three.png"], function() {
    /* Code here is called once all files are loaded. */
});
​    ​

请注意,如果您想支持IE7,则需要使用这个略微不那么漂亮的版本(这也适用于其他浏览器):
function preload(files, cb) {
    var len = files.length;
    $($.map(files, function(f) {
        return '<img src="'+f+'" />';
    }).join('')).load(function () {
        if(--len===0) {
            cb();
        }
    });
}

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