How often do you find that images in a website load gracefully; the kind where a loading icon first appears, and the image then fades in, once loaded? This technique can greatly boost the performance of your website. If you're not already familiar with this method, you're in luck! Today, we'll create a preloader plugin for your projects. Intrigued? Let's get started!
Step 1: Setting Up Your Workspace
First, we are going to setup the project folder for this tutorial. We'll need:
- Our main HTML file
- CSS folder for our stylesheet and loading icon ( in 'i' folder)
- JS folder for jQuery and our plugin
- IMAGES
Step 2: The HTML
We're going to start off with the HTML code.
<DOCTYPE html> <html> <head> <meta charset=utf-8" /> <title>Image Preloader</title> <script type="text/javascript" src="js/jquery-1.4.4.min.js"></script> <script type="text/javascript" src="js/jquery.preloader.js"></script> <link rel="stylesheet" href="css/preloader.css" type="text/css" /> </head> <body> <div id="container"> <h2>Preloader - Load images with style</h2> <ul id="gallery" class="clearfix"> <li><p><a href="#"><img src="images/1.jpg" /></a></p></li> <li><p><a href="#"><img src="images/2.jpg" /></a></p> </li> <li><p><a href="#"><img src="images/3.jpg" /></a></p> </li> <li><p><a href="#"><img src="images/4.jpg" /></a></p></li> <li><p><a href="#"><img src="images/5.jpg" /></a></p> </li> <li><p><a href="#"><img src="images/6.jpg" /></a></p> </li> <li><p><a href="#"><img src="images/7.jpg" /></a></p> </li> <li><p><a href="#"><img src="images/8.jpg" /></a></p> </li> <li><p><a href="#"><img src="images/9.jpg" /></a></p> </li> </ul> </div>
Nothing fancy here: just plain HTML code for a simple gallery. We have imported jQuery, our plugin jquery.preloader.js
(currently blank), and our preloader's stylesheet. To finish up, we'll add an unordered list, which will contain list items as images wrapped by an anchor tag (usually done in a website for opening a lightbox or linking to a site).
Note that the extra
p
tag wrapping each anchor is used for the purpose of styling the image; they are not explicitly required.
Step 3: The CSS
Now, we're going to create a preloader.css
stylesheet in the css
folder, and, inside that, create a subfolder i
in which we will keep our preloader icon. Preloaders.net have a nice collection of loading icons that you can choose from. Add the following code to your stylesheet:
* { margin:0; padding:0; } body { background:url(i/bg.jpg); } #container { width:960px; margin:0px auto; } h2 { font-weight:100; text-shadow:#ffffff 1px 1px 0px; text-align:center; padding:20px; font-size:32px; color:#555555; border-bottom:1px dashed #ccc; margin-bottom:30px; font-family: Georgia, "Times New Roman", Times, serif ; }
First, we have created a 960px
centered container, and have added a background to the page. Additionally, we've added some basic styling to the title ( h2
tag ).
Styling the Gallery
Next, we'll style the gallery and, while we are at it, throw in some CSS3 goodness.
#gallery { list-style:none; } #gallery li { background:#e8e8e8; float:left; display:block; border:1px solid #d7d7d7; -moz-border-radius:4px; -webkit-border-radius:4px; border-radius:4px; -webkit-box-shadow:1px 1px 6px #ddd; -moz-box-shadow:1px 1px 6px #ddd; box-shadow:1px 1px 6px #ddd; margin:15px 56px; padding:0; } #gallery li p { border:1px solid #fff; -moz-border-radius:4px; -webkit-border-radius:4px; border-radius:4px; margin:0; padding:7px; } #gallery li a { display:block; color:#fff; text-decoration:none; padding:0; } #gallery img { width:315px; height:210px; margin:0; padding:0; }
At this point, our gallery should look like so:
Setting up the Preloader Class
Let's create a preloader
class that will be responsible for showing the loading icon, while images are loading.
.preloader { background:url(i/89.gif) center center no-repeat #ffffff; display:inline-block; }
The preloader element's display
property must be set to block
or inline block
; otherwise, the loading icon won't show.
Step 4: Writing the Plugin
Let's begin by creating the plugin structure and options.
Allowing for customization options makes a plugin far more flexible for the user.
We start off with the base structure:
$.fn.preloader = function(options){ var defaults = { delay:200, preload_parent:"a", check_timer:300, ondone:function(){ }, oneachload:function(image){ }, fadein:500 }; // variables declaration and precaching images and parent container var options = $.extend(defaults, options), }
Our Options
- delay - Successive delay between fading in images
- preload_parent - Add preload class to the parent mentioned. If not found, the image is wrapped within an anchor tag
- ondone - Callback to be executed when all the images are loaded
- oneachload - Called when each image is loaded with image as the parameter
- fadein - Fade in animation duration
Step 5: Variables
Next, we declare and precache the variables that we will be using in the rest of the plugin.
var defaults = { delay:200, preload_parent:"a", check_timer:300, ondone:function(){ }, oneachload:function(image){ }, fadein:500 }; // variables declaration and precaching images and parent container var options = $.extend(defaults, options), root = $(this), images = root.find("img").css( {"visibility":"hidden", opacity:0} ), timer, counter = 0, i=0 , checkFlag = [], delaySum = options.delay;
First, we precache the root element (always a best practice), then find the images (also making them hidden), and finally declare the variables which will be explained in greater detail as we counter them.
There are two things worth noting here: you might initially think the easiest solution is to hide the images, and then fade them in, rather than jumping through all of this code. However, the problem is that, if we hide the images, the browser marks the space they used to occupy as empty, and thus the layout, itself, is messed up when they're eventually faded in. Okay, well what if we used opacity
to "show" and "hide" the images? That's a better practice, though, some versions of IE doesn't like this method.
Step 6: Adding Preloader Class
We now will iterate over each image element, and check if its parent is the one mentioned in the option. If so, we add our preloader class to it; else, we wrap the image within an anchor tag with a class of preloader
.
images.each(function(){ var $this = $(this); if( $this.parent( options.preload_parent ).length==0 ) { $this.wrap("<a class='preloader' />"); } else { $this.parent().addClass("preloader"); } checkFlag[i++] = false; }); images = $.makeArray(images);
Here, we are using an array checkFlag
, and are setting each array's item value to false
. Its use will be made clear as you move along.
Step 7: Bringing it All Together
We'll now implement what actually happens behind the scenes. There is a boolean
property, called complete
, associated with the image object. When the image has been loaded completely, this boolean is set to true
. So, we keep checking this property for each image, and, if it is indeed set to true
, we fade in that image.
We can use the setInterval
function to continuously determine whether the images have been loaded or not. This is where the check_timer
option comes in: it maps directly to our timer's frequency.
An image also has an onload
event associated with it; you're probably wondering why we aren't using it. The reason is because some browsers don't work well with that event; as such, we're skipping it. We need a solution that works like a charm across all browsers. We start off with:
init = function(){ timer = setInterval(function(){} },options.check_timer);
timer
is the variable which will reference the timer. This is needed in order to eventually stop the timer. This function is declared along with all the variables.
Checking Each Image
We'll iterate through the array and check each image's complete
property to determine whether it has finished downloading. If it has been downloaded, we will set it to visible and fade in slowly. When the animation has ended, we remove the preloader class from its parent.
for(i=0; i<images.length; i++) { if(images[i].complete == true) { $(images[i]).css("visibility","visible") .delay(delaySum) .animate({opacity:1}, options.fadein, function(){ $(this) .parent() .removeClass("preloader"); }); } }
There's a tiny issue here: the timer will continue to check -- even after the images have all been loaded.To counter this, we'll add a counter variable, and increment it after each image has been loaded. This way, we can check if the counter variable is equal to the size of the images array. If that's the case, we stop.
timer = setInterval(function(){ if(counter>=checkFlag.length) { clearInterval(timer); options.ondone(); return; } for( i=0; i<images.length; i++) { if(images[i].complete==true) { $(images[i]) .css("visibility","visible") .delay(delaySum) .animate({opacity:1}, options.fadein, function(){ $(this) .parent() .removeClass("preloader"); }); counter++; } } },options.check_timer)
However, there's another small issue now. Our timer may stop earlier than expected; if one image has been loaded, its complete
property has been set to true
and the counter thus increments by 1. Now, when the loop runs the next time, the image is already loaded, the complete
property is set totrue
, and, thus, the loop will run twice! To overcome this problem, we use the checkFlag
array. When an image is loaded, we will set checkFlag
to true
, and set the condition for the counter to increment only on the condition that the checkFlag
value is false
. So counter is incremented only once: when an image is loaded for the first time.
timer = setInterval(function () { if (counter & gt; = checkFlag.length) { clearInterval(timer); options.ondone(); return; } for (i = 0; i & lt; images.length; i++) { if (images[i].complete == true) { if (checkFlag[i] == false) { checkFlag[i] = true; options.oneachload(images[i]); counter++; delaySum = delaySum + options.delay; } $(images[i]).css("visibility", "visible").delay(delaySum).animate({ opacity: 1 }, options.fadein, function () { $(this).parent().removeClass("preloader"); }); } } }, options.check_timer);
Note that we call the ondone
function when the counter flag is greater than the array's length - i.e. when all the images are loaded. When the counter is incremented, oneachload
is called with the current image passed as the parameter.
Step 8: The Easy Part
Finally, in this step, we call the init();
function at the end of the plugin.
init(); // called at the last line of plugin
That's all; we have made a fully working preloading plugin, and its size is less than 2kb. Still, one problem remains: the loading icon image is loaded randomly. We don't want that. In the next section we will take care of that.
Step 9: Going the Extra Mile
To fix the problem mentioned above, we'll load the icon first, and then call the init
function. But the loading icon is a background image, so we inject it as an image in the page, while keeping it hidden. When it loads, we call the init
function. We're essentially preloading the icon itself.
var icon = jQuery("<img />", { id: 'loadingicon', src: 'css/i/89.gif' }).hide().appendTo("body"); timer = setInterval(function () { if (icon[0].complete == true) { clearInterval(timer); init(); icon.remove(); return; } }, 100);
First we create an image object with an id
of loadingicon
, and a source
pointing to the path of the loading icon. Then, we append it the body
and initially hide it. Lastly, we set the interval to check if the icon has been loaded or not. If it has, we kill the timer, and start preloading the images. Don't forget to remove that icon too!
Conclusion
With that last step, we're done! This functionality works in all browsers, just as expected, and degrades gracefully. Just be sure set the preloader element's display
property to block
or inline-block
. It is now ready to be used in your projects. Thanks for reading!
Comments