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:

  1. I would use document.images to get all images and, for ones that have the rotator-specific className, to identify (domElement.parentNode) and obtain the containing div, which will give me it’s id.
  2. I would use the parentNode.id of the class="rotation" images to create an object with sets (by container ids) I can use to store references to the img nodes.
  3. Use closure scope to stay out of the global scope, as well as be able to share variables between the closure-scoped functions.
  4. 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

Leave a Comment