I wanted to improve my slideshow with image preloading. What I wanted was something that resembles a queue for preloading.
On a blog I found a piece by Rebecca Murphey, she seems a very smart programmer. I picked up on the idea of triggering events, then read some remarks on binding data to an element, and on event delegation.
Being a noob, I concocted thus :
Each element I use to run the ‘slideshow’ has three basic functions,
- AjaxLoadImages : loads the xml data ( image urls) into $(elem).data()
- displayImage : replaces the css background-image url
- preLoad : creates an img element in the document with the image url as source, to force the browser to download the picture so once I put it in the css background the image file is already downloaded, that stops the flickering
The latter two run on a timer, more about that later on in the post.
With bind(), I tie the event handler function to an element, and can use my own events. I can delegate the handling of events up the dom tree by leaving the handler of the lower elements void and binding a handler with the same name to a higher level element, like body.
-
$("div").bind('preLoad', function(e) {});
-
$("div").bind('displayImage', function(e){});
-
$("div").bind('AjaxLoadImages', function(e){});
In ‘bubbling up’ the element data of the element where the event was triggered is sent along. I can access that elements .data() (with my image list etc.) through the generic $(event.target) object.
-
//AjaxLoadImages is a http-get that retrieves
-
//an xml file, and adds the links in it to the .data()
-
//attribute of the div
-
-
//I trigger the event on the div, which is handed upward
-
//to this function, with the div as 'target'
-
-
$("body").bind('AjaxLoadImages', function(e, fname) {
-
var target = $(e.target);
-
-
$.get(fname, {}, function(xml) {
-
var counter = 0;
-
$('entry', xml).each(function(i) {
-
counter++;
-
//key = (x + counter)
-
$(e.target).data(('x'+counter).toString(), $(this).find("link").text());
-
});
-
-
//some general data
-
$(e.target).data("preload", "0");
-
$(e.target).data("currentimage", "1");
-
$(e.target).data("countimage", counter.toString());
-
});
-
});
-
-
-
//displayImage retrieves the 'currentimage' value
-
//which, with an added X,
-
//is the key of the imageurl in the .data() element
-
//(which is key=>value array)
-
-
//Once I have it, I can replace the background image url
-
//in the css
-
-
$("body").bind('displayImage', function(e) {
-
var target = $(e.target);
-
$(e.target).css('background-image', 'url(' + $(e.target).data(('x'+ target.data("currentimage") ).toString()) + ')');
-
});
-
-
//preload does the same trick, with the source attribute
-
//of an image I add to force the browser to preload
-
$("body").bind('preLoad', function(e) {
-
$('<img />').attr({
-
src: $(e.target).data(('x' + $(e.target).data("currentimage")).toString()) });
-
});
the xml file is plain :
<?xml version=”1.0″ encoding=”UTF-8″?>
<xmlslides>
<entry>
<link>http://www.juust.org/slides/one.png</link>
</entry>
<entry>
<link>http://www.juust.org/slides/two.png</link>
</entry>
…
</xmlslides>
That sets up the basic data and functions for the div elements.
Now to make it run.
Timing, even with the SetInterval method, was gonna be a pain as it seems I can not tie SetInterval to an element. Luckily (blair mitchelmore?) was so kind as to write a jQuery plugin to handle multiple timers.
With that, I can write one event handler on body level that assigns each element that comes bubbling up a function on a timer interval to handle the display and preloading, as if it were a loop.
-
//binding the event with void handler
-
$("div").bind('ScheduleSlides', function(e){});
-
-
///…then binding the actual event handler to body,
-
//param holds the interval
-
$('body').bind("ScheduleSlides", function(e, param) {
-
-
var target = $(e.target);
-
-
//runs a function on set intervals for the target element
-
target.everyTime(param, function(i) {
-
-
//display the current image by triggering displayImage,
-
//which bubbles up to the body event handler
-
var imgcount = target.data("currentimage");
-
target.trigger('displayImage');
-
-
//increase the functions internal counter
-
imgcount++;
-
-
//set currentimage to the increased counter
-
target.data("currentimage", (imgcount).toString());
-
-
//if at the end of the image list,
-
//reset currentimage
-
//and end preloading
-
if( target.data("currentimage") == target.data("countimage") ) {
-
target.data("preload", "1");
-
target.data("currentimage", "1");
-
};
-
-
//preload the new image if necessary
-
if(target.data("preload") == "0") {
-
target.trigger('preLoad');
-
};
-
});
-
});
All that remains is to load an xml file and trigger the scheduling…
-
$("#dia_show").trigger(
-
"AjaxLoadImages",
-
"http://www.juust.org/slides/a.xml"
-
);
-
$("#dia_show").trigger('ScheduleSlides', 1000);
and another one…
-
$("#dia_show_2").trigger(
-
"AjaxLoadImages",
-
"http://www.juust.org/slides/b.xml"
-
);
-
$("#dia_show_2").trigger('ScheduleSlides', 2000);
I really wonder why I wrote it that way, but it seems to work without the flickering bit and without having to code the links in css or in the source.
I put the test page and the source of it on the server.
I read some remarks on other blogs that after adding 14 img elements IE throws a stack overflow, I haven’t looked into that.