Improving User Experience: Preload Images
Jan Lehmann | March 9, 2011
With the increasing trend towards building desktop-like experiences, the number of images being included into web sites is growing dramatically. Just think about all of those dialogs, tabs, tooltips, sliders or calendar controls which are necessary to meet these requirements.
Web site performance and usability can benefit from preloading this bunch of images, thus avoiding any kind of flickering or fragmentation.
Frequently used Techniques
There are two frequently used techniques to preload images:
CSS sprites
CSS Sprites is a technique used to limit the number of HTTP requests generated by a web page. To realize this, all images states (such as default, rollover and active of a button) are saved into one image file. This image is positioned and cropped according to the element state by CSS rules.
But there is an important drawback. Only images which are present from the very beginning (the first loading of a page) are affected. All other dynamically loaded images which may appear later aren’t loaded and will possibly cause a delay. So this technique is in line with static websites and can especially be used to reduce the amount of images.
JavaScript
Another technique can be applied by using the JavaScript image object. Using this method, you put the URL of each image displayed on the web site into an array.
var myImages = ["image_1.jpg', 'image_2.jpg', 'image_3.jpg', ...];
Then you loop through the array and create an image object for each URL. By doing this you can ensure that all of the images are cached by the browser and are available for later use without lagging.
for (var i = 0; i <= myImages.length; i++) {
var img = new Image();
img.src = myImages[i];
}
The time consuming part of this solution is to put every image URL into the array and to subsequently maintain the consistency of the array. Every change made to the web site (e.g. added images or changed image URLs) have to be reflected in the array. This can be exhausting if you imagine a constantly changing application with hundreds of images.
Automated image preloading with progress indicator
A more attractive way is to automate the collecting of image URLs.
The first step is to analyze all linked style sheets and inline styles of a web page. Therefore, looping through the members of the “document.styleSheets” object offers access to every style sheet that is in use.
for (var i = 0; i < document.styleSheets.length; i++){
Now it’s important to bring the base directory of the linked style sheet to light. This is required later on to retrieve the absolute URL of each image.
var baseURL = getBaseURL(document.styleSheets[i].href);
For browser compatibility it’s indispensable to check whether the “cssRules” or the “rules” object of each style sheet contains data.
if (document.styleSheets[i].cssRules) {
cssRules = document.styleSheets[i].cssRules
}
else if (document.styleSheets[i].rules) {
cssRules = document.styleSheets[i].rules
}
Each CSS rule is parsed to decide if it isn’t empty and, more important, if it’s an image-related rule. A simple regular expression does the job by extracting the image URL.
var cssRule = cssRules[j].style.cssText.match(/[^\(]+\.(gif|jpg|jpeg|png)/g);
Then, every image URL gathered by this analysis is pushed into an array to be processed in the next steps.
function analyzeCSSFiles() {
var cssRules; // CSS rules
for (var i = 0; i < document.styleSheets.length; i++) { // loop through all linked/inline stylesheets
var baseURL = getBaseURL(document.styleSheets[i].href); // get stylesheet's base URL
// get CSS rules
if (document.styleSheets[i].cssRules) {
cssRules = document.styleSheets[i].cssRules
}
else if (document.styleSheets[i].rules) {
cssRules = document.styleSheets[i].rules
}
// loop through all CSS rules
for (var j = 0; j < cssRules.length; j++) {
if (cssRules[j].style && cssRules[j].style.cssText) {
// extract only image related CSS rules
// parse rules string and extract image URL
var cssRule = cssRules[j].style.cssText.match(/[^\(]+\.(gif|jpg|jpeg|png)/g);
if (cssRule) {
// add image URL to array
cssRule = (cssRule + "").replace("\"", "")
imageURLs.push(baseURL + cssRule);
}
}
}
}
}
To retrieve the required absolute URL of each image, which will be pushed into the array, we implement the “getBaseUrl” function which extracts the absolute directory of each CSS file.
function getBaseURL(cssLink) {
cssLink = (cssLink) ? cssLink : 'window.location.href'; // window.location.href for inline style definitions
var urlParts = cssLink.split('/'); // split link at '/' into an array
urlParts.pop(); // remove file path from URL array
var baseURL = urlParts.join('/'); // create base URL (rejoin URL parts)
if (baseURL != "")
{
baseURL += '/'; // expand URL with a '/'
}
return baseURL;
}
Now we loop through the collection and create a JavaScript image object for each of the web sites images to preload it and to consequently force the browser to cache it. Please note the use of “setTimeout” to ensure that every image is loaded completely before loading the next one standing in line. Remember that many browsers cannot handle more than 2 requests at the same time. To avoid possible deadlocks we attach a handler to the “load”, “onreadystatechange” and “error” event by using the Query bind method. In case of missing images that couldn’t be loaded the “errorTimer” gets you back on the right track again.
function preloadImages() {
if (imageURLs && imageURLs.length && imageURLs[imagesLoaded]) { // if image URLs array isn't empty
var img = new Image(); // create a new imgage object
img.src = imageURLs[imagesLoaded]; // set the image source to the extracted image URL
setTimeout(function () {
if (!img.complete) {
jQuery(img).bind('onreadystatechange load error', onImageLoaded); // bind event handler
} else {
onImageLoaded(); // image loaded successfully, continue with the next one
}
errorTimer = setTimeout(onImageLoaded, 1000); // handle missing files (load the next image after 1 second)
}, 25);
}
else {
showPreloadedImages();
}
}
function onImageLoaded() {
clearTimeout(errorTimer); // clear error timer
imagesLoaded++; // increase image counter
preloadImages(); // preload next image
}
To improve the user´s experience while preloading is running, we modify the “onImageLoaded” event handler to interact with a progress bar.
First, include the jQuery UI style sheet and JavaScript files needed to show up a progress bar.
<link type="text/css" href="css/ui-lightness/jquery-ui-1.8.6.custom.css" rel="stylesheet" />
<script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.8.6.custom.min.js"></script>
Then initialize the progress bar in the document ready event handler.
$(document).ready(function () {
$("#progressbar").progressbar({ value: 0 }); // initialize progress bar
});
The last thing to do is to extend the “onImageLoaded” event handler. After each loaded image the progress value is calculated to update the progress bar is adjusted to.
For illustrating the success of this preloading method, every image is added to the page. After completing the whole operation all images are displayed simultaneously.
function onImageLoaded() {
clearTimeout(errorTimer); // clear error timer
$("#imagelist").append("<span>" + imagesLoaded + ": </span><img src='" + imageURLs[imagesLoaded] + "'/>"); // append image tag to image list
imagesLoaded++; // increase image counter
var percent = parseInt(100 * imagesLoaded / (imageURLs.length - 1)); // calculate progress
$("#progressbar").progressbar({ value: percent }); // refresh progress bar
$("#progressbarTitle").text("Preloading progress: " + percent + "%");
preloadImages(); // preload next image
}
A small step to get better
Preloading Images is a simple way to improve user experience and make your website look more professional. By automating the analysis of CSS rules you can handle quite a number of images without having a great deal to do.
A progress bar helps users to estimate loading times and keeps them away of being displeased. Therefore, jQuery offers great widgets like the progress bar, which can easily be adapted to meet your demands. Using them will definitely brighten up your website.
Hopefully this article has given you a stimulus to check if your project can benefit from using the described techniques
The linked zip file contains all needed files to run a demonstration of preloading images. Just download, unzip and then open preloading.html in your browser. Clicking the “Start preloading” button will start the demonstration.
With a few simple changes made to the script you’ll be able to integrate it into your own project.
About the Author
Jan-Frederic Lehmann is a ASP.Net developer and a jQuery enthusiast from Berlin, Germany. He focuses on C#, SQL-Server, HTML, CSS, JavaScript and agile practices. Web performance optimization and Rich Internet Applications are two of his favorite topics.
Jan works at InMediasP GmbH, an independent consulting company that specialises in strategies, processes, technology, and tools for the Virtual Product Creation.
Find Jan on:
- Twitter: @lehmjan