Multiple jQuery slideshows won't stay in sync

248 views Asked by At

I'm using a simple jQuery script to run a fade-in/out slideshow. (I'm a nubee at jQ.) I am running 3 instances of the slide show to control 3 separate slide areas. The slides are timed with fadeIn, delay and fadeOut. Here are the controlling code lines from each script:

slides.eq(slideCount).fadeIn(1000).delay(6000).fadeOut(1000, function(){
slides.eq(slideCount).fadeIn(2250).delay(3500).fadeOut(2250, function(){
slides.eq(slideCount).fadeIn(3000).delay(2000).fadeOut(3000, function(){

The idea is that each slide has an overall screen time of 8 seconds. It starts out great, but if I let it run for a while, the 3 slides will fall out of sync. That undermines the layout of the page. I can see that my assumption that adding the fadeIn, delay and fadeOut times together is flawed by a small increment. Does anyone know if there is an adjustment to correct this?

You can see the current slideshows here: Slideshow. If you watch the display for a few minutes it will be fine, it probably takes about 15 minutes to start getting out of sync. One solution would be for the page to refresh after x number of iterations. Is there a way to add that to the script? Any ideas would be appreciated.

Here's the full script:

$(function() {
  var slides = $('.slideShowA>li');
  var slideCount = 0;
  var totalSlides = slides.length;
  var slideCache = [];

  (function preloader() {
    if (slideCount < totalSlides) {
      // Load Images
      slideCache[slideCount] = new Image();
      slideCache[slideCount].src = slides.eq(slideCount).find('img').attr('src');
      slideCache[slideCount].onload = function() {
        slideCount++;
        preloader();
      }
    } else {
      // Run the slideshow
      slideCount = 0;
      SlideShow();
    }
  }());

  function SlideShow() {
    // Your code goes here
    slides.eq(slideCount).fadeIn(1000).delay(6000).fadeOut(1000, function() {
      slideCount < totalSlides - 1 ? slideCount++ : slideCount = 0;
      SlideShow();
    });
  }
});
1

There are 1 answers

4
John S On BEST ANSWER

You need to treat the three separate slideshows as one slideshow that shows three images at a time. That shouldn't be too difficult since the three lists have the same number of images.

I'm not sure if it matters, but you could also try preloading your images asynchronously. That is, instead of waiting for each image to get loaded before loading the next one, you could load them all at once. You can use JQuery's Deferred object to execute code after they have all loaded.

Assuming your HTML looks like this:

<div id="media-col">
    <div>
        <ul>
            <li><img ... /></li>
            <li><img ... /></li>
            <li><img ... /></li>
            <li><img ... /></li>
        </ul>
    </div>
    <div>
        <ul>
            <li><img ... /></li>
            <li><img ... /></li>
            <li><img ... /></li>
            <li><img ... /></li>
        </ul>
    </div>
    <div>
        <ul>
            <li><img ... /></li>
            <li><img ... /></li>
            <li><img ... /></li>
            <li><img ... /></li>
        </ul>
    </div>
</div>

And you have the following CSS:

#media-col li {
    opacity: 0;
    display: none;
}

Your code could look like this:

$(function() {
    var TIMES = [
        { preDelay:    0, fadeIn: 3000, delay: 2000, fadeOut: 3000 },
        { preDelay: 1500, fadeIn:  500, delay: 4000, fadeOut:  500 },
        { preDelay: 1000, fadeIn: 1000, delay: 4000, fadeOut: 1000 }
    ];

    var $container = $('#media-col');

    // This function preloads an image and returns a Deferred object that is resolved
    // when the image's onload function is called.
    function preloadImage(url) {
        return $.Deferred(function(deferred) {
            var image = new Image();
            image.src = url;
            image.onload = function() {
                image.onload = null;
                deferred.resolve(image);
            }
        });
    }

    // Asynchronously preload all the images for the slideshows.
    var preloads = $container.find('img').map(function() {
        return preloadImage($(this).attr('src'));
    }).get();

    // This will start the slideshow when all the images have been preloaded.
    $.when.apply($, preloads).done(function() {
        var $lists = $container.find('ul'),
            slideCount = $lists.first().children().length;
        (function showSlide(slideIndex) {
            var $items = $lists.children(':nth-child(' + slideCount + 'n - ' + (slideCount - 1 - slideIndex) + ')');
            var promises = $items.show().map(function(i) {
                var t = TIMES[i];
                return $(this).delay(t.preDelay).fadeTo(t.fadeIn, 1).delay(t.delay).fadeTo(t.fadeOut, 0).promise();
            });
            $.when.apply($, promises).done(function() {
                $items.hide();
                showSlide((slideIndex + 1) % slideCount);
            });
        })(0);
    });
});

To synchronize the animations, the code above uses $.when() and the fact that .promise() returns a promise that is resolved when the animations on a JQuery object are complete..

Rather than use .fadeOut(), the code above uses .fadeTo(). Then .hide() can be called when all the animations are complete. That way, all the items are hidden at the same time. This also allows for the "pre" delay. If you don't want that, you can easily remove it.

jsfiddle