Image Loading

4Feb

Background

Daniel Mee requested a tutorial and writes:

I have a large image (500k). I have a loading gif (little rotating circle thingy from ajaxload.info). I want the animated gif to be swapped with the large image once it's loaded. The event may be on page load or may be some button onClick...

This is similar to how a LightBox would work, except Daniel wants complete control of the the load event.

This tutorial will show how to load images in the background, and once loaded handle the event and create your own response.

Basic Version

In our basic version, we will have a single div containing a loading spinner and once our large image is loaded (in the background) we will remove the spinner and insert our image.

There's a few ways to approach the loading screen, two of which are:

  1. Use a background image on the holder div, this way we can easily centre align horizontally and vertically using CSS, rather than adding extra markup.
  2. Adding a styled div in our holder div, then remove the entire block of markup when the image loads.

I've provided a screencast explaining how to achieve this (though it is based on the CSS version, also shows how to do this with a separate loading div).

Watch the jQuery basic image load screencast (alternative flash version)

View the demo and source code used in the screencast

Note that in the demonstration as I am simulating loading a slow to load image by including a script tag at the bottom of the markup. In your real world version, you obviously would not include it.

HTML Markup

The markup (segment) that we're using is extremely simple. Note, however, I've included a 'loading' class on the div. This will be manipulated in our jQuery once the image has loaded behind the scenes:

<div id="loader" class="loading"></div>

CSS

For the demo, I've set the height and width of the empty div, but also included a background image which indicates something is loading.

You can use any kind of image - I've seen applications use much larger animated gifs to simulate a loading message box.

DIV#loader {
  border: 1px solid #ccc;
  width: 500px;
  height: 500px;
}

/** 
 * While we're having the loading class set.
 * Removig it, will remove the loading message
 */
DIV#loader.loading {
  background: url(images/spinner.gif) no-repeat center center;
}

jQuery

The jQuery's job is to:

  1. Load the image in the background
  2. Hook a load event
  3. Once the image has loaded, strip the loading class and insert the image
// when the DOM is ready
$(function () {
  var img = new Image();
  
  // wrap our new image in jQuery, then:
  $(img)
    // once the image has loaded, execute this code
    .load(function () {
      // set the image hidden by default    
      $(this).hide();
    
      // with the holding div #loader, apply:
      $('#loader')
        // remove the loading class (so no background spinner), 
        .removeClass('loading')
        // then insert our image
        .append(this);
    
      // fade our image in to create a nice effect
      $(this).fadeIn();
    })
    
    // if there was an error loading the image, react accordingly
    .error(function () {
      // notify the user that the image could not be loaded
    })
    
    // *finally*, set the src attribute of the new image to our image
    .attr('src', 'images/headshot.jpg');
});

Added Functionality

The jQuery code used is the constructs of our load function. If we wanted to style the image in any way, add classes or run an Ajax request before showing the image, it would go inside the .load() function.

This functionality could also be placed inside of a .click() event handler or anything else if you wanted to trigger the image loading.

For example, if you had a particular image map linked to this image that you wanted to request via Ajax the contents of the load function would be this:

$(img)
  .load(function () {
    $(this).hide();
    
    // our bespoke ajax hit that's required with the image
    // it will return the HTML for the <map> element and
    // linked <area> element.
    $.ajax({
      url: 'image-map.php',
      data: 'img=' + i.src, // the image url links up in our fake database
      dataType: 'html',
      success: function (html) {
        // because we're inside of the success function, we must refer
        // to the image as 'img' (defined originally), rather than 'this'
        $('#loader')
          .removeClass('loading')
          .append(img)
          .append(html);
          
        // now show the image
        $(img).fadeIn();
      }
    });
  })
  // code continues as before

23 Comments

  1. Daniel
    February 5, 2008 at 00:23

    A great tutorial that I'll definitely be using on my project. The screencasts are really cool (I'm also glad you didn't edit out your debugging process - that actually helps to teach more things too :) Thanks for responding to the request form and contributing to the community knowledge base.

  2. Giuseppe
    February 5, 2008 at 21:22

    Great as usual. You are making an awesome work thanks to the simplicity of the "instructions" and because of you don't short or jump any step assuming it is "well know" by a developer… I am not a developer, just a designer, so thanks a lot for not jumping any step and keeping it clear! ;)

  3. Jeff
    February 6, 2008 at 22:59

    Fantastic; this is exactly what I was looking for. I'm going to be incorporating a modified version of this with my company's newly revamped website. Thanks!

  4. Ryan
    February 7, 2008 at 08:35

    Thanks for the screencasts. Much easier to understand than written explanations. One question, what if the image has been loaded (hitting the back button in a series of pages for instance)? The .load() function keeps a cached image from fading in. Is there an easy if() statement that could catch this?

    Thanks again.

  5. Fazal Khan
    February 7, 2008 at 20:41

    is there a way to get the amount of data thats been loaded? this would help to make a loading bar. I know you can in Flash, but I suppose it's a real headache in Javascript. I was even thinking that you could use a small flash movie just to get the bytes from and pass them to jQuery and then continue as normal...any ideas? great simple tutorials by the way!

  6. Jonathan
    February 8, 2008 at 09:11

    Hi, great explanation, but i don't understand "particular image map linked to this image".

    Is the img.load() function responsible for loading the image? if yes, than what is the other ajax requesting for?

  7. Remy
    February 9, 2008 at 00:18

    @Ryan - do you mean if the .load() function is pulling the image from a cache? Either way, it will load the image with the effect. Though I know of a bug in jQuery that doesn't allow the fade effect to work in Safari, which I've worked around using .style.display = "none" instead of .hide().

    @Fazal - sadly not. That said, how big can the image be? If you've got a decent loading gif, and perhaps a setInterval() that updates a loading message it should really be enough.

    @Jonathan - the Ajax requesting was just to show that more can be done between the time the image is actually loaded, and the time it's actually shown. In Daniel's request, he went on to describe the requirement for extra information linked to the image, which I could see being done via an Ajax request.

    I hope that helps you all :-)

  8. Guillaume Stricher
    February 10, 2008 at 13:16

    It's better not to define your animated .gif through css but through the dom, as Safari versions prior to 3 can't handle animated gif as bgnd... This is a known bug... It's even better and easier to add it in your page through innerHTML since this is the fastest method onload.

  9. David
    February 13, 2008 at 02:46

    Is there a callback to load() that fires when the image is actually loaded? I'm trying to fetch height/width and I can only do that when the image has fully loaded and available from cache.

    Trying $(this).width() inside will return zero.

  10. Remy
    February 16, 2008 at 20:09

    @David - if you wrap your newly created image in jQuery, you can use the .load() function against your callback, as per the screencast.

    When you try $(this).width() has the image been inserted in to the DOM yet? I'm assuming not. The .width() function is based on the computed style, which I believe (though may well be wrong!) is based from the document. So if the image hasn't been inserted, it won't have a width you can access yet.

  11. ruvan
    February 23, 2008 at 07:54

    great article, was really helpful!! thanks!!

  12. Chris
    February 26, 2008 at 20:17

    If you wanted to load a page with 10+ pictures using this method how would you go about it? (I am completely new to jQuery)

  13. Tobias
    March 2, 2008 at 17:51

    Thanks a lot man, this was definitely one of the best tutorials I've seen in a while! Wouldn't it be possibly to extend this tutorial to have a simple slideshow? Using an array of picture urls taken from db query or reading the contents of a folder? I'm a newbie at jquery, about 10 hours of learning, but I'll try to do that building on your tutorial, possibly even including a cross fade effect plugin.

    If you think this question would be interesting to more people, why not append your great tutorial...

    Thanks again!

  14. Joseph
    March 23, 2008 at 09:41

    Thats exactly what i needed to do, thanks alot. Only one question what if i had many images in different div ids?

  15. Blavel
    March 26, 2008 at 05:11

    Great tutorial. This really helped me!

    Question: how do you go about making a page like this still work (albeit without the loading or fancy effects) for people with javascript disabled / no javascript? I would like to be able to make a view picture page that will show a loading spinner and fade in effect for people with javascript, but for people without javascript - just load the image the traditional way.

    I have tried having a img tag within the document to start with and then removing it - but if the image has already been cached the load function often isn't fast enough to remove it before it flashes up on the screen.

    Thanks!

  16. Epic Alex :: Web Design
    April 1, 2008 at 13:47

    Is it possible to use this to load a background image on my body tag, its a pretty hefty image, and its slowing my page load down.

    My thinking is that i don't bother with the loading gif part, and just delay the image being shown until it is loaded,

    any advice? Thanks

  17. Raimund
    April 4, 2008 at 09:15

    this was exactly, what i missed on the main page of jquery: a simple example how to implement a image while running a load-function. thanks raimund

  18. Tom Deater
    April 7, 2008 at 18:29

    Great article. One "gotcha" that I've run into before that you might need to watch out for: IE doesn't seem to want to fire the "load" event when the image is pulled from the browser cache. I code around it by doing something like this:

    function imgLoadCallback() { // do image loaded stuff here }

    $(img).load(imgLoadCallback);

    if (img[0].complete) { imgLoadCallback() }

    YMMV, but that's the basic pattern I've been using for awhile.

  19. Tom Deater
    April 7, 2008 at 18:30

    Oops, just for clarity that second line should have been:

    var img = $(foo).load(imgLoadCallback);

  20. mptre
    April 11, 2008 at 16:17

    Hello, first of thanks for a great tutorial. I'm trying to use this script on a site I'm buidling atm. But I got three questions regarding the script.

    1. I can't get the fadeIn function to work i Safari. Read the comments and tried using .style.display = “none” instead of hide(). But it doesn't seem to work out for me. Should the whole line look like this? $(this).style.display = 'none';

    2. Check out the source-code for your example page. Just curious could you post the action=delay code? Would be glad if you could use that to.

    3. Last question is it possible to load the script several times? I want to be able to load the script after the head tag. Since I'm using the script on multiply images, that I get from a mysql-database. I want to use the image-load on every single picture I this case I need to the definie the path to image several times since I loading more then one picture. So is possible to load the script severeal times? Like loading the script after the head-element.

    Thanks!

  21. mptre
    April 11, 2008 at 16:23

    Found the answer by myself regaring my first question. To solve the Safari FadeIn-bug use this line.

    $(this).css('display','none');

  22. Remy
    April 11, 2008 at 16:39

    @mptre - re: question two - I don't recommend you use that "feature". It was just a way I could force the latency to happen for the demo.

    However, since you asked - here it is: http://codedumper.com/ojivu

  23. mptre
    April 11, 2008 at 17:04

    @Remy - alright, In that case I will skip the delay function. Thanks anyway.

    Anyone got an answer to my third question?

Leave a comment