Friday, June 20th, 2008

Preloading Images with jQuery

Category: jQuery

<p>Just the other day I was chatting with a colleague about how to go about pre-loading images before rendering a cool JavaScript-driven animation; Scott Jehl from the Filament Group wrote in to tell us about a jQuery plug-in that they use for just that purpose:

We recently put out a super handy jQuery plugin for pre-caching all images referenced in linked/imported CSS files.
It’s particularly useful for apps with overlays, yet-to-be embedded content, etc. It sure beats maintaining arrays of image paths or making complex CSS sprites.

The script iterates through each rule in each stylesheet attached to the current page and if the rule’s value contains an image URL, it loads the image, thus ensuring it’s available in the cache when used in the document.

javascript
< view plain text >
  1. jQuery.preloadCssImages = function(){
  2.     var allImgs = [];//new array for all the image urls  
  3.     var k = 0; //iterator for adding images
  4.     var sheets = document.styleSheets;//array of stylesheets
  5.    
  6.     for(var i = 0; i<sheets .length; i++){//loop through each stylesheet
  7.         var cssPile = '';//create large string of all css rules in sheet
  8.         var csshref = (sheets[i].href) ? sheets[i].href : 'window.location.href';
  9.         var baseURLarr = csshref.split('/');//split href at / to make array
  10.         baseURLarr.pop();//remove file path from baseURL array
  11.         var baseURL = baseURLarr.join('/');//create base url for the images in this sheet (css file's dir)
  12.         if(baseURL!="") baseURL+='/'; //tack on a / if needed
  13.         if(document.styleSheets[i].cssRules){//w3
  14.             var thisSheetRules = document.styleSheets[i].cssRules; //w3
  15.             for(var j = 0; j<thisSheetRules.length; j++){
  16.                 cssPile+= thisSheetRules[j].cssText;
  17.             }
  18.         }
  19.         else {
  20.             cssPile+= document.styleSheets[i].cssText;
  21.         }
  22.        
  23.         //parse cssPile for image urls and load them into the DOM
  24.         var imgUrls = cssPile.match(/[^\(]+\.(gif|jpg|jpeg|png)/g);//reg ex to get a string of between a "(" and a ".filename"
  25.         if(imgUrls != null && imgUrls.length>0 && imgUrls != ''){//loop array
  26.             var arr = jQuery.makeArray(imgUrls);//create array from regex obj    
  27.             jQuery(arr).each(function(){
  28.                 allImgs[k] = new Image(); //new img obj
  29.                 allImgs[k].src = (this[0] == '/' || this.match('http://')) ? this : baseURL + this; //set src either absolute or rel to css dir
  30.                 k++;
  31.             });
  32.         }
  33.     }//loop
  34.     return allImgs;
  35. }

Handy.

Related Content:

19 Comments »

Comments feed TrackBack URI

very handy indeed

Comment by Jamie — June 20, 2008

I see a problem with this.. there might be images that we don’t use on the page but this will still load them.

dont get me wrong.. i am planning to use this plugin.

Comment by so1oonnet — June 20, 2008

@so1oonnet: Maybe the easiest fix in that case is to put all the images you want to pre-load into a “/preload/” directory and adjust the RegEx to ignore all other image paths.

Comment by Ben Galbraith — June 20, 2008

surely css sprites prevents this issue anyway ?

Comment by seengee — June 20, 2008

@ so1oonnet: It’s a valid point and something you’ll need to keep in mind when using this plugin. At Filament, we do a lot of app development where most of the images specified in the CSS are likely to appear in the page you’re currently on at some point, such as overlay backgrounds, tooltips, injected content, etc. This might not be a smart plugin to use if your CSS contains images for many sections of a large static website. It’s all in the implementation.

@Ben: That’s a good idea, we’re thinking of adding an “ignore” argument so you could pass in directories or even image names that shouldn’t be preloaded. That’d be nice too so no regex changes would be needed per use!

@seengee: Glad you mentioned sprites. As we point out in the article, CSS sprites are great for many things such as button states, but don’t make a lot of sense for tying together images that have no relation to each other. Consider background images for content that will soon be injected into the DOM, or overlays, tooltips, the list goes on. Sprites can be annoying to maintain too, particularly if you need to edit just one of the states of a sprite. But like I said, it’s good to still use sprites where they make sense because it’ll be one less request to the server for each state. For everything else, just attach this script and call $.preloadCssImages(); and it’ll take care of all of em.

Comment by ScottJehl — June 20, 2008

@Scott
I’d counter that sprites are certainly useful even for images that aren’t contextually related. As long as you don’t care about a user hotlinking to a particular background image and it being what they expect, I don’t see an argument as to why you wouldn’t use sprites for all your background images. These sprites from iGoogle and The Onion come to mind.
Regardless though, this script can be useful, especially in a user’s idle time.

I’ve used a similar script for when I want to preload a set of defined image paths:

// imgstopreload is an array containing URLs of the images you want
for (x in imgstopreload){ (new Image).src = imgstopreload[x]; }

Comment by PaulIrish — June 20, 2008

What difference does it make whether the images are related to each other or not? CSS Sprites help anyway.

And as for maintaining the CSS Sprites, that can be automated.

Comment by Nosredna — June 20, 2008

@PaulIrish: Thanks for the examples. That’s great if you have the resources/time to manage a sprite like that, it would certainly speed up page load to have them all in one hit. Wow, sure seems like a headache to maintain an image like that though (that Onion one is crazy!). What happens when an image is no longer needed on the site: Full CSS rewrite or continue to download the whole thing anyway? Also some images might need alpha transparency while others don’t; one big PNG would be a huge hit if unnecessary on some images. What about repeating background tiles (can’t use sprites for those)?

Icon sprites like that can be really difficult with text resizing too, as other states start to leak into view (as text grows) unless you space the images out massive amounts in the sprite.

Anyway, this script will help in situations that I’d consider to be more common in app dev. But I’m certainly not saying sprites are a bad idea wherever they can be used.

As for your array example, we discussed that approach in the article as its definitely the common approach to image preloading. It works great of course but requires keeping a separate array of paths in your JS to correspond to the CSS. Doing that requires more maintenance work as a site design evolves (as do sprites).

Thanks for the examples though. It’s interesting to see how some sites approach the issue!

Comment by ScottJehl — June 20, 2008

Another approach would be to use the LINK PREFETCH attribute. This isn’t supported on all browsers (for example, IE), but then for IE the jQuery module could prefetch those images, and for FF it wouldn’t have to do anything. A downside is keeping stylesheets and prefetch links in sync.

Comment by souders — June 20, 2008

@Nosredna: If you automatically generate a sprite, can you then auto-generate all of the positioning in the CSS to match it? Seems unlikely but interesting if possible.

Like I said, sprites are an excellent tool. I’d just argue that they don’t make sense for every situation. I named many of such situations above:

– repeating backgrounds (aside from a sprite with repeating stacked button states, repeating tiles aren’t very sprite-friendly)
– percentage-positioned backgrounds (background-position: left 50%; to center an icon next to text)
– alpha transparent images among other images that don’t need it
– image size optimization (some images might be better as jpg, gif, png)
– unrelated images in one sprite (perhaps you’re right that this one doesn’t matter so much, but in many cases it makes sense for organization purposes to group sprites by purpose)
– Deprecated imagery in a sprite
– Maintenance time keeping up with sprite image locations, etc
– Potential learning curve for client handoff

This script provides a sort of “set it and forget it” approach which is nice when maintaining a site.

But if you can do all of your images in sprites – go for it, the page will require fewer requests.

We offer this script as one more tool in the dev toolbox. Use as needed. :)

Comment by ScottJehl — June 20, 2008

@souders: Interesting! Perhaps the plugin could simply look for that attribute and run this if the browser needs it… Any standards backing on that?

Comment by ScottJehl — June 20, 2008

As mentioned in the original blog post comments:
Prototype port is here: http://pastie.org/209498

Comment by jdalton — June 20, 2008

@jdalton: Thanks, it’s a great port. Like I said before, you’ve found places we should optimize in ours.

Comment by ScottJehl — June 20, 2008

>>@Nosredna: If you automatically generate a sprite, can you then auto-generate all of the positioning in the CSS to match it? Seems unlikely but interesting if possible.

Of course. Any decent CSS sprite builts the classes you need. That way you don’t have to make any changes to your code.

Ajaxian has featured at least one such tool, but there are many of them.

Comment by Nosredna — June 20, 2008

@Nosredna: Sounds cool, I’ll definitely check it out. Regardless, this script is useful where sprites can’t be used. As noted above.

Comment by ScottJehl — June 20, 2008

Yeah. If, for some reason you can’t use sprites, it makes sense.

Comment by Nosredna — June 20, 2008

Update: This script has been updated and now includes options for a progress bar and text status updates on load progress.
For example:
6 of 12 loaded (50%)
Now Loading: myImage.png

…along with a percentage bar.
Example here:
http://www.filamentgroup.com/examples/preloadImages/index_v3.php

Comment by ScottJehl — June 21, 2008

Whenever I see the flicker that means an image hasn’t loaded yet, I get an instant headache and want to yell at the developer. Kudos!

Comment by richtaur — June 21, 2008

Great plugin. I needed jquery code to simply preload images using jquery and insert them later in my document. I kept comming across this plugin. For those who need to preload images not images used in CSS but some specific images read this article:

http://jquery-howto.blogspot.com/2009/02/preload-images-with-jquery.html

Comment by jQueryHowto — April 6, 2009

Leave a comment

You must be logged in to post a comment.