jQuery image Preloader Plus Callbacks

February 11, 2009 in jQuery Plugins

Hi all, After comments on the previous version by Roberto, I looked into creating callbacks within the code so that it can be used in the way that Roberto had outlined, also I used it as a way to improve my knowledge a little.

After a while I got stuck and figured out a potential method thanks to ai-a in #Javascript on UnderNet, then I spoke to Remy Sharp on Twitter and always being impressed with the work he does on his site (http://www.jqueryfordesigners.com) I asked politely if he would give my code a quick look over. He did so, and so much more besides.

So I present to you now v0.95 of the Image Pre-loader which is now Split into 3 functions (rather than the 2 from before)

So the main functionality is the same as the one from http://binarykitten.jkrswebsolutions.co.uk/2009/01/06/jquery-image-preloader/ but now has the following functions

  1. $.preLoadImages – Preload the passed list of images, calling the passed call back function when all images are preloaded
  2. $.preLoadCSSImages – Preload all images found within the stylesheets of the document, then call the passed call back function when all the images are preloaded
  3. $.preLoadAllImages – Processes the stylesheet images and then if passed additional images, will process them too. When all are complete will call the passed callback function.

Here’s the actual code now for the plugin:

/* jQuery.preloader - v0.95 - K Reeve aka BinaryKitten
*
* v0.95
* 	# Note - keeping below v1 as really not sure that I consider it public usable.
* 	# But it saying that it does the job it was intended to do.
* 	Added Completion of loading callback.
* 	Main Reworking With Thanks to Remy Sharp of jQuery for Designers
*
*
* v0.9
* 	Fixed .toString being .toSteing
*
* v0.8
*		Fixed sheet.href being null error (was causing issues in FF3RC1)
*
* v0.7
*		Remade the preLoadImages from jQuery to DOM
*
* v0.6
* 		Fixed IE6 Compatability!
*		Moved from jQuery to DOM
*
* v0.5
* 		Shifted the additionalimages loader in the preLoadAllImages so it wasn't called multiple times
* 		Created secondary .preLoadImages to handle additionalimages and so it can be called on itself
*/

(function ($) {
	$.preLoadImages = function(imageList,callback) {
		var pic = [], i, total, loaded = 0;
		if (typeof imageList != 'undefined') {
			if ($.isArray(imageList)) {
				total = imageList.length; // used later
					for (i=0; i < total; i++) {
						pic[i] = new Image();
						pic[i].onload = function() {
							loaded++; // should never hit a race condition due to JS's non-threaded nature
							if (loaded == total) {
								if ($.isFunction(callback)) {
									callback();
								}
							}
						};
						pic[i].src = imageList[i];
					}
			}
			else {
				pic[0] = new Image();
				pic[0].onload = function() {
					if ($.isFunction(callback)) {
						callback();
					}
				}
				pic[0].src = imageList;
			}
		}
		pic = undefined;
	};

	$.preLoadCSSImages = function(callback) {
		var pic = [], i, imageList = [], loaded = 0, total, regex = new RegExp("url\((.*)\)",'i'),spl;
		var cssSheets = document.styleSheets, path,myRules,Rule,match,txt,img,sheetIdx,ruleIdx;
		for (sheetIdx=0;sheetIdx < cssSheets.length;sheetIdx++){
			var sheet = cssSheets[sheetIdx];
			if (typeof sheet.href == 'string' && sheet.href.length > 0) {
				spl = sheet.href.split('/');spl.pop();path = spl.join('/')+'/';
			}
			else {
				path = './';
			}
			myRules = sheet.cssRules ? sheet.cssRules : sheet.rules;
			for (ruleIdx=0;ruleIdx < myRules.length;ruleIdx++) {
				Rule = myRules[ruleIdx];
				txt = Rule.cssText ? Rule.cssText : Rule.style.cssText;
				match = regex.exec(txt);
				if (match != null) {
					img = match[1].substring(1,match[1].indexOf(')',1));
					if (img.substring(0,4) == 'http') {
						imageList[imageList.length] = img;
					}
					else if ( match[1].substring(1,2) == '/') {
						var p2 = path.split('/');p2.pop();p2.pop();p2x = p2.join("/");
						imageList[imageList.length] = p2x+img;
					}
					else {
						imageList[imageList.length] = path+img;
					}
				}
			};
		};

		total = imageList.length; // used later
		for (i=0; i < total; i++) {
			pic[i] = new Image();
			pic[i].onload = function() {
				loaded++; // should never hit a race condition due to JS's non-threaded nature
				if (loaded == total) {
					if ($.isFunction(callback)) {
						callback();
					}
				}
			};
			pic[i].src = imageList[i];
		}

	};
	$.preLoadAllImages = function(imageList,callback) {
		if (typeof imageList != 'undefined') {
			if ($.isFunction(imageList)) {
				callback = imageList;
			}
			else if (!$.isArray(imageList)) {
				imageList = [imageList];
			}
		}
		$.preLoadCSSImages(function(){
			if (imageList.length > 0) {
				$.preLoadImages(imageList,function(){
					callback();
				});
			}
			else {
				callback();
			}
		});
	}
})(jQuery);

So now with these in place we can call them as so:

     $.preLoadImages(
          [
               'http://www.google.co.uk/intl/en_uk/images/logo.gif',
               'http://l.yimg.com/eur.yimg.com/i/uk/hp/yahoo1.png',
               'http://tk2.stc.s-msn.com/br/hp/11/en-us/css/i/msn_b2.gif'
          ],function(){
               alert('All Passed Images Loaded');
          }
     );
     $.preLoadCSSImages(function(){
          alert('All CSS Images Loaded');
     });
     $.preLoadAllImages(
          [
               'http://www.google.co.uk/intl/en_uk/images/logo.gif',
               'http://l.yimg.com/eur.yimg.com/i/uk/hp/yahoo1.png',
               'http://tk2.stc.s-msn.com/br/hp/11/en-us/css/i/msn_b2.gif'
          ],function(){
               alert('All Passed Images and All CSS Images Loaded');
          }
     );

To match up with Robert’s request for a loader image…

$.preLoadImages('/images/loader.gif',function() {
  /* Pre Load the loader gif first */
  $('<img />').attr({
  	src:'/images/loader.gif',
  	id:'loader'
  }).appendTo('#position');
  /* now preload stuff */
  $.preLoadCSSImages(function() {
     $('#loader').remove();
  })
});

Hopefully you have found this useful, as usual.. comments gratefully received.

[edit]
Thanks to Roberto for pointing out that the source had become corrupted in the post