JavaScript to make a fast-running image slideshow?
There were some issues I saw with your code or approach, so I decided to redo it with the approach I would take. For instance:
- I would use
document.images
to get all images and, for ones that have the rotator-specificclassName
, to identify (domElement.parentNode
) and obtain the containingdiv
, which will give me it'sid
. - I would use the
parentNode.id
of theclass="rotation"
images to create an object with sets (by containerid
s) I can use to store references to theimg
nodes. - Use closure scope to stay out of the global scope, as well as be able to share variables between the closure-scoped functions.
- Use variable functions to setup the handler and callback functions.
Let me know if you have any questions or find something that doesn't work.
<div id="rotator1" class="rotation blackbg">
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/6/6e/Brandenburger_Tor_2004.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/a/ad/Cegonha_alsaciana.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/d/da/CrayonLogs.jpg" />
</div>
<div id="rotator2" class="rotation blackbg">
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/1/17/Bobbahn_ep.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/9/90/DS_Citro%C3%ABn.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/f/f0/DeutzFahr_Ladewagen_K_7.39.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/c/c7/DenglerSW-Peach-faced-Lovebird-20051026-1280x960.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/4/4d/FA-18F_Breaking_SoundBarrier.jpg" />
</div>
var slideshows = function(){
var timeouts = {},
imgs;
function preloadImages(list){
var loading = list,
img,
loaded = {},
newimages = [];
var imageloaded = function(){
// this here is one of the new Image()s we created
// earlier; it's not the "real" image on the screen.
// So I put the reference to the actual image it represents
// on the screen in the rel attribute so I can use it's
// reference; I just have to use this.rel to get it.
var parent = this.rel.parentNode;
// Keeping track of the individual sets loaded.
loaded[parent.id]++;
// Here is where this/self gets it's context from, when
// we .call(parent, 0). It's the parentNode to the image
// we've referenced above. This should only run once,
// when the last image has loaded for the set.
if (loaded[parent.id] == loading[parent.id].length) {
animateSlideshow.call(parent, 0);
}
};
var imagenotloaded = function(){
// this.rel is the reference to the actual image we
// have in the DOM, so we'll set the error flag on it.
this.rel['imageerror'] = true;
imageloaded.call(this);
};
for (var load in loading) {
// loaded is equivalent to imgs.sets, so load is the
// id for the container.
loaded[load] = [];
// Now we're going to loop over every image in the
// current set, creating a Javascript image object
// to initiate the download of the file and tell us
// when it's finished. Not the newimages[i].rel = img
// part.
for (var i = 0, l = loading[load].length; i < l; i++) {
img = loading[load][i];
newimages[i] = new Image();
newimages[i].onload = imageloaded;
newimages[i].onerror = imagenotloaded;
newimages[i].rel = img;
newimages[i].src = img.src;
}
}
}
var animateSlideshow = function(current) {
// this could be used instead of self. I was doing
// something else at first, but making this copy
// of the context (this) is not necessary with this
// code. It doesn't hurt either.
var self = this;
// Our current context is the containing div, so
// this.id/self.id will give us the key to the correct
// group in imgs.sets, and the current argument will
// tell us with image in that list we're currently
// working with. First, we hide the last displayed
// image.
imgs.sets[self.id][current].style.display = 'none';
// Increment to get the next image.
current++;
// If we're at the end, let's move back to the
// beginning of the list.
if (current >= imgs.sets[self.id].length) {
current = 0;
}
// This skips images which had an error on load. The
// test for this in the markup above is the third image
// in rotator1, which is not an image url.
if (imgs.sets[self.id][current].imageerror == true) {
// See how I'm passing self using .call()? This sets
// the context for the next animateSlideshow() call,
// which allows this/self to work on the correct div
// container.
animateSlideshow.call(self, current);
return;
}
imgs.sets[self.id][current].style.display = 'inline';
// Everything is organized by the self.id key, event
// saving the references to the timeouts.
timeouts[self.id] = setTimeout(function(){
animateSlideshow.call(self, current);
}, 100);
};
function getImages(){
var list = document.images,
img,
data = {sets: {}, allimages: []},
parent;
// document.images gives me an array of references to all
// img elements on the page. Let's loop through and create
// an array of the relevant img elements, keying/grouping on the
// parent element's id attribute.
for (var i = 0, l = list.length; i < l; i++){
img = list[i];
parent = img.parentNode;
// parent should now be a reference to the containing div
// for the current img element. parent.id should give us
// rotator1 or rotator2 in the demo markup.
if (parent.className.indexOf('rotation') !== -1) {
if (!data.sets[parent.id]) {
data.sets[parent.id] = [];
}
// Let's put the img reference into the appropriate
// imgs.sets. I also put the img.src into an index
// container in data.allimages; this is also a remnant
// of a previous approach I took. It could probably be
// removed unless you need it.
data.sets[parent.id].push(img);
data.allimages.push(img.src);
}
}
return data;
}
function initializeSlideshows(){
imgs = getImages();
preloadImages(imgs.sets);
}
initializeSlideshows();
};
$.onDomReady(slideshows);
http://jsfiddle.net/DLz92/1