Preloading An Image & Displaying With A Automatic Resizing Container With jQuery

Posted 2 months, 1 week ago at 9:38 pm. 11 comments

EDIT: There has been a report of this code not working in IE6. Well turns out it didn’t workin any of the IE browsers. Go to the bottom to find out how to fix it.

Ok so the title is feckin’ huge, but I couldn’t think of anything else to call it. Anyway this post is all about how to make a image preload (load in the background) and then animate the box it will fit in to match it’s size.

I’ll post a link to an example at the end but first lets get down to the coding. First you’ll need jQuery, most of you probably know where to get it but if not you can download the latest version from jQuery’s Google code site.

The HTML

So let’s go. First we want to set up so HTML. Let’s go simple:

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

There we go. It’s very simple. We have a <div> with the id ‘gallery’ to contain everything, then we have another <div> with the id of ‘loader & the class ‘loading’. I’ll explain why in a minute.

The CSS

Ok, now let’s go on to the CSS. I’ll show the code first and then explain it:

body {
	background-color:black;
}
#gallery {
	width:500px;
	margin:0 auto;
	padding:0;
}
div#loader {
	width: 300px;
        height: 400px;
	border: 3px #ccc double;
}
div#loader.loading {
	background: url(img/24-1.gif) no-repeat center center;
}

Well the body bit is obvious. :) The id ‘gallery’ just centers it using auto margins and gives a width of 500px. Then id ‘loader’ gives a permanent width & height to the loader and a border of 3px with a double effect in gray, obviously you can change them as you wish. We then define a ‘loading’ class only to work when added to the div with the id ‘loader’. This loads a background gif, an animated loader, to signify that the image is loading.

The jQuery

Next comes the hard bit. Well it’s only hard if you don’t know how. :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$(function() {
  var img = new Image();
  $(img).load(function() {
    $(this).hide();
    $('#loader').removeClass('loading').html(this);
    $('.fullSizeImage').wrap('<a></a>');
    $('#loader a').attr('href', 'path/to/img/');
    iHeight = $('.fullSizeImage').height();
    iWidth = $('.fullSizeImage').width();
    if($('#loader').height() == iHeight && $('#loader').width() == iWidth) {
	$(this).fadeIn();
    } else {
	$('#loader').animate({height: iHeight}, 500).animate({width: iWidth}, 500);
	$(this).animate({opacity: 1}, 1000).fadeIn();
    }
  }).attr('src', 'path/to/img').addClass('fullSizeImage');
});

So first we go inside jQuery’s on DOM ready function. This is a short hand way of writing it, for more on what exactly the ready function is try here.

We then create a new image and store it as img. Then hand that img as a jQuery selector, since it doesn’t yet exist this tells jQuery to create it. We then ask it do to a few things when the image has finished loading. First we hide the image from view, this is so we can make some fancy transitions, then we ask jQuery to target the element with the id ‘loader’ and remove the ‘loading’ class. This will remove the loading animation, we want this to happen since the image has finished loading, we then tell it to place this (which refers to the image) into the HTML of the div ‘loader’.

Next we target something with a class called ‘fullSizeImage’. Now this might seem odd but we add that class to the image we are creating later in the code, we can reference it now since it will have already executed the later commands before it does these ones. We tell jQuery to wrap an %lt;a> around it. Then we target and <a> inside the ‘loader’, of which there is only one, and give it a path to the full size image. Since our gallery will be pretty it can’t show the full image only a scaled down one, this is why we give the user the option to click and see the full image.

We then get the height and the width of ‘fullSizeImage’, which is the image, and store it in 2 variables. You can get the width & height of most elements including images using the function shown. We then ask if the width & height of the image are both exactly the same size as the ‘loader’ container. If they are then there is no point in resizing, so we fade the image in nicely. If not we use jQuery animation function to animate the height & then the width, if needed. You could do both at once by adding them to the same animation function, but I think seperate looks better. Then we fade in the image again.

Since we used hide() to hide the image, we have to use fadeIn() to get the image back, but fade doesn’t queue onto the animate function, basically the image would fade in and resize at the same time and that’s not what we want. To solve this we get jQuery to do animate a useless property for the length of time of both resizes, in this case 1 second. You could also possibly use jQuerys ‘:animated’ selector (jQuery 1.2 above) but I’ll leave that for another tutorial.

Next you could give it something to do in case it can’t load the image by using:

.error(function() { 
  //things here
})

just before attr but I have left it out. Next we set the path to the image we want to load, this is more than likely the full image just resized by CSS, which you can do using the following:

selector img {
  width: 300px;
  height: auto;
}

where selector is a element, id or class limiter. Or you can do it like I have in this example, using the attr to add a set width or height to the <img> tag. Finally we add the class of ‘fullSizeImage’.

This all may seem a little confusing and to be honest it’s hard to explain in typed words what exactly is going on, but I hope that what I have wrote makes some sense and helps you understand how exactly this code works.

This is, as you might have guessed, an excerpt from an image gallery prototype I am still working on for my sisters, Ebay Artist Lisa-Marie, new website. I’m not showing it all since I’m not exactly finished yet but this bit I am happy with, in some extent. If you can see any bugs or you just can’t get it to work then please leave a comment or if you have MSN contact me via that for a instant response. ;)

You can view a fully implemented demo here. It could look nicer but it works & aesthetics isn’t exactly my forte. The images are of Californian pop duo Aj & Aly, if you were wondering.

Tip: if you need a loading whirly thing, you can get one from ajaxload.info.

FIX: Here’s how to fix that problem with the Internet Explorer browsers. You need to completely change the code to the following:

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$(function() {
if($('#loader:animated').length != 0) {
		return false;
	} else {
		var img = new Image();
		$(img).load(function() {
			$(this).hide();
			$('#loader').removeClass('loading').html(this);
			$('.fullSizeImage').wrap('<a class="fullImageLink"></a>');
			$('#loader a').attr('href', imgPath);
			$(this).css({"height": 'auto', "width": 400});
			iHeight = $(this).height();
			lHeight = $('#loader').height();
			if(lHeight == iHeight) {
				$(this).fadeIn();
			} else {
				tempImg = $(this);
				$('#loader').animate({height: iHeight}, 500).animate({width: 400}, 500, function() {
					$(tempImg).fadeIn();
				});
			}
		}).attr('src', imgPath).addClass('fullSizeImage');
	}
}

This is the second time I have changed the code thanks to the pain in the royal ass that is Internet Explorer. I suppose I should thank it though as it pointed out a slight flaw in the code, well I say flaw, it was actually IE’s inability to understand a certain part of code… I hate that browser. :(

Anyway a huge thank you to Nat for showing me & making me aware of the errors in my code. :) I said I would fix it and hopefully I have. It has been tested to the best of my ability in IETester but since I have Vista I can’t truly test the code in IE6. Oh and the new bit about :animated means it requires the newest version of jQuery and also stops IE from having a sh*t fit when pressing lots of thumbnails at once, which you shouldn’t do anyway, but should that happen it won’t break. :)

Anyway hopefully that has fixed it, you’ll hopefully be able to see an advanced version of this code in action of Lisa Marie’s portfolio when it’s finished. Enjoy. :)

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • StumbleUpon
  • Google
  • Furl
  • del.icio.us
  • Facebook

11 Replies

  1. Hey Paul,

    Thanks for this tutorial. It provides great functionality and it looks great too. I’m having trouble getting my version working on IE6. It seems to be the loader div having trouble re-sizing to the images. However, it only happens when I go to a previous image in my code. Here is my code:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    $(function() {
    	picsArray = ["pic1.jpg","pic1.jpg","pic1.jpg","pic1.jpg"];
     
    	picPosition = 0;
    	preloadImage(picsArray[picPosition]);
     
    	$("div.controls a.next").click(function() {
    		if (picPosition  0) {
    			picPosition--;
    			preloadImage(picsArray[picPosition]);
    		}
    		return false;
    	});		
    });
    function preloadImage(imgPath) {
    	  var img = new Image();
    	  $(img).load(function() {
    		$(this).hide();
    		$('#loader').removeClass('loading').html(this);
    		iHeight = $('.fullSizeImage').height();
    		iWidth = $('.fullSizeImage').width();
    		if($('#loader').height() == iHeight &amp;&amp; $('#loader').width() == iWidth) {
    			$(this).fadeIn();
    		} else {
    			$('#loader').animate({height: iHeight}, 500).animate({width: iWidth}, 500);
    			$(this).animate({opacity: 1}, 1000).fadeIn();
    		}
    	}).error(function() {
    		$('#status').css('display', 'block').html('Image Load Failed');
    	}).attr('src', imgPath).attr('width', 400).addClass('fullSizeImage');
    }

    So there isn’t a smaller gallery as in your example. Instead, the user clicks on a “Next” and “Previous” button to cycle through the images.

    Your example seems to throw a wobbly when I load it up in IE6 too, though. Any ideas?

    Thanks again,
    Nat

  2. Hey Nat,

    Sorry Nat, I had no idea that the code didn’t work in IE6 since I have no way to test it. I have vista, came with my new computer, and I hadn’t found a way to test in IE6.

    I’ve just managed to test now though and I see there is a problem. Oddly the problem occurs in every single version of IE including 7 and 8 beta 1… I’ll see if I can fix it.

    Sorry about the problem.
    Paul

  3. Great, thanks for the quick reply.

    For testing different versions of IE, you may want to check out this little utility - http://lifehacker.com/395353/ietester-renders-sites-like-internet-explorer-55-through-8 . Using IE7, it renders pages in different versions of IE from 5.5 to 8 beta1. I’m not sure if it treats Javascript the same way as it renders pages, but it’s worth a try.

  4. Funnily enough IEtester is what i used just after I wrote the comment. I have fixed the problem now(hopefully) if you check the post the fix is on the bottom.

    I don’t know if that will fix your other problem as well but hopefully it will. :)

    If not just give me another shout and I’ll be glad to help you in anyway I can. ;)

    Edit: Forgot to mention the code doesn’t work in IE5.5. It doesn’t recognise the CSS commands I use. Nevermind though IE5.5 is a bit of a browsing relic anyway, I would hope no-one is using it anymore…

    Good luck., with IE6 you’ll need it, lol.
    Paul

  5. It’s okay, I don’t mind not supporting IE5.5. Hopefully within a year or two we can stop supporting IE6 as designers.

    Thanks for the code revisions.
    I found another bug in IE6 on your gallery and mine though. I think it happens when you try loading an image that you have already loaded before. In IE6 the image will sometimes display, but other times it wont. I will get a Javascript error in the console along the lines of:

    Line: 32
    Char: 2337
    Error: ‘null’ is null or not an object

    I couldn’t figure out why this error was being generated when the same loader function is used, but only with a different string for the path. The only thing I could think of is that the “img” var that is created in the preloadImage function is somehow conflicting because it has created an image from that pathname before.

  6. Hi Nat,

    I’ll try again, lol… I have just managed to get the error you described but only through fast and furious clicking of the thumbnails. It’s a very odd problem though since I can’t see any errors that would even refer to a NULL object.

    I’ll give the code a full going over though and see if I can find out what’s going on. If you find anything else please give me a buzz. I’ll leave another comment & modify the code again if I fix it.

  7. The code has been updated again. The problem with IE went deeper into the code than I though and it turned out to be a incompatibility problem with a few points in the code.

    One was the queueing method I had used which I have replaced with a better version I recently learned & the other was a targeting method I had used that IE didn’t seem to understand but Firefox did.

    Nevermind we all know that Firefox is better than IE, lol.

    I hope that fixes your problems Nat and anyone else who may have been using the code. If not let me know of any errors again and I’ll do my best to fix them. :)

  8. I believe that 1 problem with IE6 is that the “onload” event isn’t fired for images which are loaded from cache, this might explain the problem which Nat was having. I don’t know if jquery has solved this problem?

  9. Hi Peter,

    I can’t really say but the main problem was that I was resizing the image by setting the width attribute of the img tag when it was generated. Usually this would work but IE doesn’t seem to be able to generate the height to match when the width is changed on-the-fly.

    This was solved by changing the width in CSS and autoing the height. The final problem was that I was tring to get the newly created height of the image before it had resized it. This meant it returned the original size of the image and not the newly resized height.

    I never actually use the onload event as jQuery has it’s own called $(document).ready() which can be written as $(function(){}) and has been tested it IE6 and works thank god, lol.

    Thanks for that little bit of info though, I didn’t know IE didn’t fire an onload for images. Could come in handy methinks. ;)

  10. Awesome, those fixes worked. Thanks Paul!

    And I believe that Peter is onto something. When I go back in my array of pictures, the old picture does not fade out. It disappears immediately and the new one fades in. And the .loading class is never loaded either - so the Ajax loader .gif never displays. I haven’t had too much time to investigate it, but I think it may have to do with loading the image from the cache.

  11. Nope fraid not. It is caused by the fact that the fadeOut command in jQuery is an unqueueable command.

    jQuery’s animation queue does not queue the fadeOut and fadeIn commands. For example:

    $('#animateit').animate({'top': 200}, 1000);
    $('#animateit').fadeOut('fast');

    When ran you would think that it would move the div animateit 200px from the top and then fadeOut fast. Unfortunately not, because fadeOut is unqueueable animate will start to run but it will immediately fade out before that animation finishes.

    The reason you never see the loader depends on your internet connection speed and the size of the images you’re using. If they are small and you have a high speed connection the image will have loaded near to instantly so there is no need to show the loader. Plus I have tested it on extremely large images (jpegs of 4000 x 4000px) in IETester and I see the loader fine.

    There could be another reason the loader gif doesn’t show which is the fact it is the class that shows it is removed after the first image is loaded. To show the loader again you need to reattach the .loading class again on the click event that loads the next picture. For example:

    $("div.controls a.next").click(function() {
                    $('#loader').addClass('loading');
    		if (picPosition  0) {
    			picPosition--;
    			preloadImage(picsArray[picPosition]);
    		}
    		return false;
    });

    That will then show the loading class, which shows the loader, again each time a new image is shown until the preload is complete and then the preload function removes the class again as the image is finished loading.

    I hope that makes sense. The fadeOut problem should only occur if the next image loads quick enough for it to interupt the previous image. There isn’t really anyway to stop that problem though. Although you could try swapping the two fades. That’s the fadeIn in the preloadImage function and the fadeOut your using with this animate({'opacity': 1}, 500) for fadeIn and change 1 to 0 for fadeOut.

    I hope that helps you some more. Just shout if you have any more questions.

    EDIT: Here is a better example of the need to put back the .loading class. This is the code that loads the new image when a thumbnail is clicked on my example:

    function changeImage(a) {
    	img = $(a).children();
    	imgThmb = img.attr('src');
    	imgPath = imgThmb.replace(/-\d+x\d+/gi, '');
    	$('.fullSizeImage').fadeOut();
    	$('#loader').addClass('loading'); // The line required...
    	preloadImage(imgPath);
    }

    As you can see each time a new image is loaded the loading class is added back to the loader div. preloadImage holds the other part which removes the loader before fading the image in.


Leave a Reply