*

2006 / November 21st/ Image preloading revisited

I’d like to start showcasing a lot more Javascript-related content on my site, so welcome to the first post in a new category: Interface Scripting. Why not use some hot new buzzword like DOM Scripting? Well, because I won’t just be talking about DOM Scripting. I’ll be talking about the entire melting pot of Javascript, DOM, BOM, interaction between rich-media, and the 3rd dimension — time. Hopefully it’ll give you insight into my problem-solving route for writing Javascript, and allow me to answer that question of: How do you do that?

In years past, image preloading was all the rage. You’d see more MM_preloadImages than pictures of Britney at newstands. Who’s to blame anyone? It was a great method for loading up images used for rollovers. But now with navigation matrices and modern CSS-based techniques, the ways of the preloader have fallen by the wayside.

The old way

You all remember Dreamweaver’s rollover feature. It was nice, easy and pretty abstracted from the actual code you were writing. The problem was in the source output:

<body onLoad="MM_preloadImages('/assets/images/topdiscover_on.gif','/assets/images/topdevices_on.gif','/assets/images/toppersonalize_on.gif','/assets/images/topsoftware_on.gif','/assets/images/topaccesories_on.gif','/assets/images/tophelp_on.gif','/assets/images/topbuy_on.gif')">

Yuck! Not only is this code in an inline onload event handler, but it’s pretty inflexible. I can’t preload images before the page content has been loaded (since this is using window.onload, this code will execute after all page content [including images] has been loaded), and I can’t tell the code to do something after the images have been preloaded.

What I wanted to do

What I wanted to do was send in (an) image(s) to a preloader function, and tell it to execute something after this was done. For my specific purpose, I wanted to load a large hero graphic and then fade it in using some script.aculo.us effects. Personally, I like to write the usage of my Javascript functions before I write the code powering them. So let’s write out some nice calls:

Preloader.add('/images/graphics/home_large.jpg');
Preloader.onFinish(Stage.Current.Timeline.start);
Preloader.load();

I’d like this to be flexible enough so that I can add images at multiple points in the page/script as well as multiple functions that are executed after the images are loaded. The add method accepts a string or an array of image locations. The onFinish method accepts a function or an array of functions to add to the queue.

Building the Preloader

For reference, here’s the final code: Download preloader.js

var Preloader = {
  callbacks: [],
  images: [],
  loadedImages: [],
  imagesLoaded: 0,

  add: function(image){
    if (typeof image == 'string') this.images.push(image);
    if (typeof image == 'array' || typeof image == 'object'){
      for (var i=0; i< image.length; i++){
        this.images.push(image[i]);
      }
    }
  },
  onFinish: function(func){
    if (typeof func == 'function') this.callbacks.push(func);
    if (typeof func == 'array' || typeof func == 'object'){
      for (var i=0; i< func.length; i++){
        this.callbacks.push(func[i]);
      }
    }
  },
  load: function(){
    for(var i=0; i<this.images.length; i++){
      this.loadedImages[i] = new Image();
      this.loadedImages[i].onload = function(){ Preloader.checkFinished.apply(Preloader) }
      this.loadedImages[i].src = this.images[i];
    }
  },

  checkFinished: function(){
    this.imagesLoaded++;
    if (this.imagesLoaded == this.images.length) this.fireFinish();
  },
  fireFinish: function(){
    for (var i=0; i<this.callbacks.length; i++){
      this.callbacks[i]();
    }
    this.images = [];
    this.loadedImages = [];
    this.imagesLoaded = 0;
    this.callbacks = [];
  }
}

Step 1: Create your properties

Let’s start off with this bit of code:

var Preloader = {
  callbacks: [],
  images: [],
  loadedImages: [],
  imagesLoaded: 0
}

This defines the properties of the Preloader that I’m going to keep track of. I also decided to use an Object literal for my Preloader — I don’t foresee needing multiple instances of the Preloader at a time, and I’d like to keep things nice and simple from a syntax standpoint. The Object literal is very familiar.

  • callbacks: An array (shorthand []) of functions to be called after the images are done loading
  • images: An array of images that we’ll be preloading. These are actually just going to be strings representing the path of the image.
  • loadedImages: I’ll need a place to keep my Image objects that I’m creating using the preloader.
  • imagesLoaded: An internal counter for how many images are done loading.

Step 2: Define the add and onFinish methods

Here’s the code that we’ll use to create our Preloader.add(path) and Preloader.onFinish(func). I’ve lumped these together since they’re really doing the same thing — taking an input that can be a singular (string or function) or multiple (array or object) and adding it to the images and callbacks properties.

add: function(image){
  if (typeof image == 'string') this.images.push(image);
  if (typeof image == 'array' || typeof image == 'object'){
    for (var i=0; i< image.length; i++){
      this.images.push(image[i]);
    }
  }
},
onFinish: function(func){
  if (typeof func == 'function') this.callbacks.push(func);
  if (typeof func == 'array' || typeof func == 'object'){
    for (var i=0; i< func.length; i++){
      this.callbacks.push(func[i]);
    }
  }
}

This code is actually pretty simple: It first tests to see if the input is singular by using the typeof operator. If it is, it pushes the item to the end of the images/callbacks array. Otherwise, if the input is an Array or an Object, it cycles through each item and pushes the item to the images/callbacks array.

Step 3: Load the images

This next step is where all the lifting is done. We’re going to craft three separate methods to handle loading: load, checkFinished and fireFinish.

load: function(){
  for(var i=0; i<this.images.length; i++){
    this.loadedImages[i] = new Image();
    this.loadedImages[i].onload = function(){ Preloader.checkFinished.apply(Preloader) }
    this.loadedImages[i].src = this.images[i];
  }
},
checkFinished: function(){
  this.imagesLoaded++;
  if (this.imagesLoaded == this.images.length) this.fireFinish();
},
fireFinish: function(){
  for (var i=0; i<this.callbacks.length; i++){
    this.callbacks[i]();
  }
  this.images = [];
  this.loadedImages = [];
  this.imagesLoaded = 0;
  this.callbacks = [];
}

The overall functionality of this is pretty simplistic: a new Image object is created for each image to be preloaded. We’re going to override the onload event handler of the Image object with our checkFinished method.

What’s up with the Preloader.checkFinished.apply(Preloader) ?
This little snippet of code uses a built-in Javascript property apply. Basically, we’re telling it to call the method Preloader.checkFinished(), except that we want this inside the method to reference the Preloader object. That’s why we use apply with Preloader in the parameter.

The trick is in how we’re determining when we’re finished. Since we’re adding n images to our preloader that are an undefined size each — we have no way of knowing which image will finish loading last. We want to preload all of the images, so we’ll need a strategy to determine when the last image is loaded.

The way I’ve set up the logic is like this:

  1. We create the image object, and attach a callback to the checkFinished method on the onload event of the Image object.
  2. Once the image is loaded, Preloader.checkFinished(); will be called.
  3. Our checkFinished method then increments the imagesLoaded counter property. It then checks to see if imagesLoaded is the same number as the total number of images we need to preload. If it is, then the fireFinish method is called, otherwise it keeps waiting for additional checkFinished calls.
  4. The fireFinish method then cycles through each of the onFinish functions we’ve set up, and calls them.
  5. Lastly, we zero out our properties so we can use the Preloader again.

The glory of encapsulation

Now we’ve got a nice little Javascript image preloader. There’s nothing revolutionary about it by any means — except in the approach we took to get here. By approaching your Javascript as little modules of functionality, you can quickly re-use codebases with ease. In fact, shortly after writing this script, a co-worker asked if there was a preloader script I used to achieve this exact functionality. Instead of pointing to a 2,000 line javascript file, I was able to cut and paste a few lines of code and ship it off.

This is the true benefit of encapsulation. Write modular, re-usable code and you’ll save yourself work in the long haul. Also note that while I could have used a library like Prototype or YUI, I chose to keep it vanilla Javascript. Why? Simple: The benefits did not outweigh the extra overhead. It seems recently that people are blindly including massive libraries to save three or four lines of javascript. Always remember that using a library is a design decision, and doesn’t always make your life easier. As an added bonus, this script is now completely portable between projects. The only dependency is on the name of the variable Preloader.

Examples & Testing

But wait… how do I know if this thing is even working? Head on over to my example page, and go ahead and bring Sloppy along for the ride (essential tool #862) to slow down your connection. You’ll notice the image doesn’t load progressively (a little bit at a time), but rather preloads in the background until it’s done, and then shows up.

Have fun!

14 Comments

comments feed

  1. Gravatar
    justin

    November 21st | #

    And now to somehow hook that code up with something that will parse through all the CSS files and load up images there as well :)

    Seriously though, you did this entire article just to show off your 20th anniv. GTI didn’t you?? I would’ve too.

  2. Gravatar
    Thomas Aylott

    November 21st | #

    Great timing. I was just about to write something like this. Now I’m all geared up to write vanilla Javascript!

    I’ll just have to rewrite the TextMate Javascript language grammar from scratch first.

    If you wish to make an apple pie from scratch, you must first invent the universe. ;)

  3. Gravatar
    puNk!d

    November 21st | #

    the script rocks!

    but i got a question…if the downloading process takes too much time,nuthing shows up.someone may say: well,there’s nuthing here but letters..okay,let me close the window.

    can it have a loading process indicator?

  4. Gravatar
    Kyle

    November 21st | #

    puNk!d: Yep, you could do whatever you like. The script is more a method for preloading… what you do before and after is all up to you :)

    Unfortunately, we can’t show percentage done via javascript (since the browser won’t let me know how many bytes / total are done). But you could use an indeterminant indicator for sure.

  5. Gravatar
    Brutal

    November 22nd | #

    Great script!

    But what if I do want to use multiple instances of the Preloader at a time?

  6. Gravatar
    Jonathan Franks

    January 4th | #

    Great article! and very well explained. I’ve been searching online for an intelligent image preloading solution and this is the best I’ve found.

    Is the onload event of the image object standards compliant? From what I’ve seen, it seems that it isn’t. But why?? it’s so useful.

    I guess it doesn’t really matter as long as its implemented in the major browsers. (is it??)

    Thanks!

  7. Gravatar
    Kyle

    January 4th | #

    The onload handler is definitely compliant (though it’s not HTML, since inline handlers are deprecated as of XHTML Strict) — it should work in any browser made within the past decade or so :)

  8. Gravatar
    Daniel

    March 7th | #

    Great! Glad to see that it works in every major browser. Thank you so much.

  9. Gravatar
    Hans Brough

    December 18th | #

    Nice article. You obviously spent some time writing a very well thought out and easy to understand explaination. Thx.

  10. Gravatar
    Abhijeet Singh

    February 5th | #

    Hi
    can anyone help me out to get the preloader script like using here inthis website
    http://www.redflystudios.com/portfolio/web-design/ when i click on thumbnail the way of larger image is oppening, I want this complete script for some usages. I have tried hard but didn’t get any where.

    Can anyone help me out to get this script please write me at aps_158@yahoo.com

    Thanks
    abhijeet Singh

  11. Gravatar
    Prahlad Yadav

    June 22nd | #

    According to me this script is made by very talented person and they explained very well.

    This is very simple script according to the explanation given by the author. Everybody can understand the script easily from the explanation.

    Great articles.

    Cheers……………….

    Thanks.

  12. Gravatar
    Jacob Uba

    June 27th | #

    This is the best preloadeer script one can get on the internet. And thanks a a lot for your talent of explanations.

  13. Gravatar
    Nightfly

    September 2nd | #

    Very useful. You might want to submit it to http://scripteka.com/

  14. Gravatar
    Nightfly

    September 2nd | #

    BTW I can’t download http://warpspire.com/playground/preloader/preloader.js :(

Make a Comment

don’t be afraid, it’s just text

Comments are parsed with Markdown. Basic HTML is also allowed.