15

This is the code I use to preload images, I'm not sure if it's the best one. My question is, how can I fire and event, for an example alert(); dialog after is has finished loading all the images?

var preload = ["a.gif", "b.gif", "c.gif"];
    var images = [];
    for (i = 0; i < preload.length; i++) {
        images[i] = new Image();
        images[i].src = preload[i];
    }
Badr Hari
  • 8,114
  • 18
  • 67
  • 100
  • There's an interesting discussion here: http://www.webmasterworld.com/forum91/2991.htm . I'm curious what answers you'll get here. – Aleadam Apr 11 '11 at 15:36

10 Answers10

31

You can use the new "$.Deferred" if you like:

var preload = ["a.gif", "b.gif", "c.gif"];
var promises = [];
for (var i = 0; i < preload.length; i++) {
    (function(url, promise) {
        var img = new Image();
        img.onload = function() {
          promise.resolve();
        };
        img.src = url;
    })(preload[i], promises[i] = $.Deferred());
}
$.when.apply($, promises).done(function() {
  alert("All images ready sir!");
});

Might be a little risky to leave the Image objects floating around, but if so that could be fixed easily by shifting the closure. edit in fact I'll change it myself because it's bugging me :-)

Ben Clayton
  • 80,996
  • 26
  • 120
  • 129
Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 1
    A bit off topic ... I'm curious what you meant by shifting the closure. What var were your closuring before? – Matt Apr 11 '11 at 16:15
  • @Matt I had let the "img" variable be overwritten on each loop iteration; the closure was just around the onload callback. It would *probably* work that way, but it seemed like a bad thing to leave here for posterity :-) – Pointy Apr 11 '11 at 16:16
  • Made a couple of small fixes to the code (was new'ing Image twice). – Ben Clayton May 23 '13 at 07:50
  • Using jQuery promises - this is great. – Alex Oct 23 '13 at 07:54
  • Hmmm, the line `$.when.apply($, promises).done()` doesn't seem to work in IE 7. Any ideas? – dotty May 13 '14 at 13:11
  • @dotty no I don't know why it wouldn't work; do you get errors? – Pointy May 13 '14 at 14:13
  • Nope. No errors are thrown at all, it just seems to skip over it. I added an `alert()` to test it, and it never fired. – dotty May 13 '14 at 15:06
  • @dotty well are you sure that the images are loading? Also: make sure that you set up the "onload" handler *before* you set the "src" property. – Pointy May 13 '14 at 15:08
  • check my jsfiddle. https://jsfiddle.net/bababalcksheep/ds85yfww/ If images dont exist we dont even get a callback .. – django Mar 24 '16 at 07:05
  • @Pointy I seem to have a tiny glitch when I tried to use this code, have posted it as a separate question, if u get a min: http://stackoverflow.com/questions/39416666/image-preloader-callback-firing-before-all-images-have-loaded – Sajjan Sarkar Sep 09 '16 at 17:17
14

Since your tags include jQuery, here's what I would do in jQuery, with heavy inspiration from this related answer:

function preloadImages(images, callback) {
    var count = images.length;
    if(count === 0) {
        callback();
    }
    var loaded = 0;
    $.each(images, function(index, image) {
        $('<img>').attr('src', image).on('load', function() { // the first argument could also be 'load error abort' if you wanted to *always* execute the callback
            loaded++;
            if (loaded === count) {
                callback();
            }
        });
    });
};

// use whatever callback you really want as the argument
preloadImages(["a.gif", "b.gif", "c.gif"], function() {
    alert("DONE");
});

This relies on the callback to jQuery's load function.

I wouldn't use the related answer simply because I don't like mucking around with Javascript's built-in prototypes.

Willster
  • 2,526
  • 1
  • 32
  • 32
justkt
  • 14,610
  • 8
  • 42
  • 62
1

Today I needed to preload images and execute some code only after all images are loaded, but without jQuery and using Ionic2/Typescript2. Here's my solution:

// Pure Javascript Version
function loadImages(arrImagesSrc) {
    return new Promise(function (resolve, reject) {
        var arrImages = [];
        function _loadImage(src, arr) {
            var img = new Image();
            img.onload = function () { arr.push([src, img]); };
            img.onerror = function () { arr.push([src, null]); };

            img.src = src;
        }
        arrImagesSrc.forEach(function (src) {
            _loadImage(src, arrImages);
        });
        var interval_id = setInterval(function () {
            if (arrImages.length == arrImagesSrc.length) {
                clearInterval(interval_id);
                resolve(arrImages);
            }
        }, 100);
    });
}

// Ionic2 version
private loadImages(arrImagesSrc: Array<string>): Promise<Array<any>> {
    return new Promise((resolve, reject) => {
      ...
      function _loadImage(src: string, arr: Array<any>) {
        ...
      }
      ...
 }

You can use like this. Problematic url returns 'null'.

loadImages(['https://cdn2.iconfinder.com/data/icons/nodejs-1/512/nodejs-512.png', 'http://foo_url'])
.then(function(arr) {
   console.log('[???]', arr); 
})
Roque
  • 359
  • 4
  • 8
1

I've seen something used to correct behavior on Masonry nice jQuery plugin (a plugin used to make nice layout composition on pages). This plugin had problems with blocks containing images and should delay his work when the images are loaded.

First solution is to delay on the onLoad event instead of document.ready. But this event can be quite long to wait for. So they use jquery.imagesloaded.js which can detect that all images in a div are loaded; especially this very short and nice code can handle cached images which does not fire the load event sometimes.

regilero
  • 29,806
  • 6
  • 60
  • 99
0

I'm searching for techniques that work for doing this all day now, and I finally found the solution to all my problems: https://github.com/htmlhero/jQuery.preload

It can even preload sequentially for you, in batches of any size (two at a time, for example), and fire a callback each time a batch completes.

fiatjaf
  • 11,479
  • 5
  • 56
  • 72
0

jquery preloading multiple images serially can be done with a callback.

function preload_images(images_arr, f, id){
    id = typeof id !== 'undefined' ? id : 0;
    if (id == images_arr.length)
        return;
    $('<img>').attr('src', images_arr[id]).load(function(){
        console.log(id);
        f(images_arr[id], id);
        preload_images(images_arr, f, id+1);
    });
}

For example:

preload_images(["img1.jpg", "img2.jpg"], function(img_url, i){
    // i is the index of the img in the original array
    // can make some img.src = img_url
});
0

Based on answer from Ben Clayton above.

There can be case where images load fail all together and one must still be able to get callback.

Here is my revised answer. One ca easily find out hom many images loaded with success and how many ended up in error. this in callback will have that info

preloadImages(_arrOfImages, function() {
  var response = this;
  var hasError = response.errored.length + response.aborted.length;
  if (hasError > 0) {
    console.log('failed ' + hasError);
  } else {
    console.log('all loaded');
  }
});

// JSFIDDLE: https://jsfiddle.net/bababalcksheep/ds85yfww/2/

var preloadImages = function(preload, callBack) {
  var promises = [];
  var response = {
    'loaded': [],
    'errored': [],
    'aborted': []
  };
  for (var i = 0; i < preload.length; i++) {
    (function(url, promise) {
      var img = new Image();
      img.onload = function() {
        response.loaded.push(this.src);
        promise.resolve();
      };
      // Use the following callback methods to debug
      // in case of an unexpected behavior.
      img.onerror = function() {
        response.errored.push(this.src);
        promise.resolve();
      };
      img.onabort = function() {
        response.aborted.push(this.src);
        promise.resolve();
      };
      //
      img.src = url;
    })(preload[i], promises[i] = $.Deferred());
  }
  $.when.apply($, promises).done(function() {
    callBack.call(response);
  });
};
Community
  • 1
  • 1
django
  • 2,809
  • 5
  • 47
  • 80
0

Check out: http://engineeredweb.com/blog/09/12/preloading-images-jquery-and-javascript. It uses jQuery. In the comments, your direct issue is addressed by someone and a solution was posted there. I think it'll fit your needs.

Code Maverick
  • 20,171
  • 12
  • 62
  • 114
0

As an alternative you could use a CSS technique to pre-load the images as shown here:

http://perishablepress.com/press/2008/06/14/a-way-to-preload-images-without-javascript-that-is-so-much-better/

and then use the global onLoad event which is fired when everything has been loaded

Miquel
  • 4,741
  • 3
  • 20
  • 19
  • Yes, it is a good technique, but I need to load HUGE files and I want to tell the user that they are loading and alert them when it's ready. It can't be done via CSS :) – Badr Hari Apr 11 '11 at 15:53
  • Well, if I am not mistaken the onLoad event will fire when all has been loaded so within the onLoad event handler you can advise the user that all the images have been loaded. If you need to tell the user when every image has finished then you need the onload event of the image object as has been suggested – Miquel Apr 11 '11 at 15:56
0

//Here is a pre-jquery method, but jquery is bound to be simpler to write.

function loadAlbum(A, cb, err, limit){
    // A is an array of image urls;
    //cb is a callback function (optional);
    //err is an error handler (optional);
    // limit is an (optional) integer time limit in milliseconds

    if(limit) limit= new Date().getTime()+limit;
    var album= [], L= A.length, tem, url;

    while(A.length){
        tem= new Image;
        url= A.shift();
        tem.onload= function(){
            album.push(this.src);
        }
        tem.onerror= function(){
            if(typeof er== 'function') album.push(er(this.src));
            else album.push('');
        }
        tem.src= url;

        // attend to images in cache (may not fire an onload in some browsers):
        if(tem.complete && tem.width> 0){
            album.push(tem.src);
            tem.onload= '';
        }
    }
    // check the length of the loaded array of images at intervals:
    if(typeof cb== 'function'){
        window.tryAlbum= setInterval(function(){
            if(limit && limit-new Date().getTime()<0) L= album.length;
            if(L== album.length){
                clearInterval(tryAlbum);
                tryAlbum= null;
                return cb(album);
            }
        },
        100);
    }
    return album;
}
kennebec
  • 102,654
  • 32
  • 106
  • 127