Search

jQuery Infinite Carousel

Posted on 11th June 2009 — With jQuery for Designer’s redesign I decided to add a scrolling carousel that worked in the same way the carousel worked on the Apple Mac ads page. This tutorial will walk through the fundamentals of the effect and how to recreate it.

Watch

Watch jQuery Infinite Carousel screencast (Alternative flash version)

QuickTime version is approximately 70Mb, flash version is streaming.

View the demo used in the screencast

Example of Apple's get a mac carousel

Understanding the Effect

As with any complicated effect, I always start by looking at the DOM in Firebug and interact with the effect, watching for what changes take effect in the DOM.

What I noticed was the (rendered) DOM structure for the list of images was different to the delivered DOM (i.e. the original markup sent). This means that there’s some significant initialisation code running. This isn’t unexpected for an Apple effect, but there were big clues in to how to create this effect in the difference.

Most importantly the list of images (in a <ul> element) had additional <li> elements at the start of the list and at the end of the list. There were two types: clone and empty.

The second big clue was the number of cloned elements on the end of the list: it matched the number of visible items in the carousel.

In the diagram below I’ve tried to outline what’s cloned in the list, and show what’s visible. You can see how the list shows 3 items at once, but there’s 5 items in total. So the first cloned section (at the start of the list) contains two clones and a single empty clone. The end of the list has 3 clones - which represents real items 1-3:

Diagram of DOM to Apple's carousel

The Trick

Aside from building the DOM with the cloned nodes, the real trick is in the final panel of list items appearing. Because we’ve cloned the start of the list to the end of the list, when we scroll off the last items, it looks like we’ve looped back round. Once the animation completes, we change the scrollLeft position back to the real first items.

This scrollLeft isn’t visible to the user since the overflow is hidden, and so it creates the effect of being infinitely sliding from left or right (since it also works the other way around).

Markup

Remembering that we want the effect to work without JavaScript turned on, we’re going to use a pattern that we use fairly often for effects that scroll: overflows.

The default will be the overflow is visible, and we’ll use JavaScript to change the element to overflow: hidden.

<div class="infiniteCarousel">
  <div class="wrapper">
    <ul>
      <li>...</li>
      <li>...</li>
      <li>...</li>
    </ul>        
  </div>
</div>

The CSS has been marked up in a way that the overflow is visible to start off with, and that the individual <li> elements are the same fixed width. This is necessary to ensure that we always show complete images in the visible part of the carousel.

jQuery

Replicating the Effect

This is a reasonably advanced effect so we need to plan carefully what we’re trying to achieve:

  1. Markup that works without JavaScript (done!)
  2. Initialisation & capture the height and width of the first carousel item (required for calculations)
  3. Create empty list items to pad to fill every page
  4. Generate cloned items on the start and end of the list
  5. Adjust the scroll left position to show the real first item
  6. Create goto method that supports forward and backwards
  7. Generate the next/prev links & bind goto calls

The completed example is also available if you want to skip through each step.

Initialisation

We’re going to write the code as a plugin so we can release it out to the wild at some later point. So this follows the typical plugin pattern:

(function ($) {
  $.fn.myPlugin = function () {
    return this.each(function () {
      // plugin code
    };
  };
})(jQuery);

This step is used to initialise the plugin and capture all the variables we need to create this effect. This includes:

  1. jQuery instance of the overflow element
  2. Hiding the overflow
  3. jQuery instance of the list
  4. jQuery instance of the list items
  5. The first item and it’s width
  6. Number of visible items we can fit in the carousel (rounded up via Math.ceil)
  7. Current page (defaults to 1) and the number of pages (rounded up)
var $wrapper = $('> div', this).css('overflow', 'hidden'),
  $slider = $wrapper.find('> ul'),
  $items = $slider.find('> li'),
  $single = $items.filter(':first'),
  
  // outerWidth: width + padding (doesn't include margin)
  singleWidth = $single.outerWidth(), 
  
  // note: doesn't include padding or border
  visible = Math.ceil($wrapper.innerWidth() / singleWidth), 
  currentPage = 1,
  pages = Math.ceil($items.length / visible);

Padding with Empty Items

During the screencast I said that we’re making use of the repeat function from another tutorial - however that was wrong! I must have used it for something else! None the less, here is the repeat function, which we put outside of the plugin since it’s a utility function:

function repeat(str, n) {
  return new Array( n + 1 ).join( str );
}

The repeat function simple uses and array hack to repeat any string n times.

When we create the empty padding, we need to use modulus to work out how many new empty items we need to fill every page of our carousel with an item:

if (($items.length % visible) != 0) {
  $slider.append(repeat('<li class="empty" />', visible - ($items.length % visible)));
  $items = $slider.find('> li');
}

Notice that we reinitialise the $items variable. This is to ensure it contains all of the items, including our newly created empty items.

Generating Clones

As per the diagram in the introduction, we need to clone the last page of items to the start of the list, and the first page of items to the end of the list. These will be the items that we slide on to before we flip the scrollLeft position (as per the trick above).

$items.filter(':first').before($items.slice(-visible).clone().addClass('cloned'));
$items.filter(':last').after($items.slice(0, visible).clone().addClass('cloned'));
$items = $slider.find('> li'); // reselect

We’re making using the slice method that jQuery has. Notice that we first use it with a negative number:

$items.slice(-visible)

This means to take the last n items off the end of $items. Otherwise we have to pass the start and end point, so the second instance we’re saying from the start, 0, and the third as the method is zero-based indices.

Reset Scroll Left

Since we’ve created the clones at the start of the list, we need to shift the scroll left back to the real element, otherwise it would look like the carousel is sitting on the last page.

$wrapper.scrollLeft(singleWidth * visible);

Go To Method

The gotoPage method will handle going forward and backwards and handle jumping several pages at a time.

We’re going to use the animate method and only animate the scrollLeft so that it smoothly scrolls. We could add custom easin if we wanted at a later date.

We need to establish the following:

  1. Direction we’re going to slide
  2. Number of pages to slide
  3. The relative scroll left value

Once the animation has completed, we also need to maintain the currentPage variable we created on initialisation.

If the page number is greater than the number of pages (i.e. we’re on our cloned first page), then reset the scroll left to the real first page, and set the page number to 1.

Equally if we have hit the beginning of the list (i.e. we’re showing the cloned last page), then we reset the scroll left to the real last page, and set the page number to the total number of pages.

function gotoPage(page) {
  var dir = page < currentPage ? -1 : 1,
    n = Math.abs(currentPage - page),
    left = singleWidth * dir * visible * n;
  
  $wrapper.filter(':not(:animated)').animate({
    scrollLeft : '+=' + left
  }, 500, function () {
    if (page == 0) {
      $wrapper.scrollLeft(singleWidth * visible * pages);
      page = pages;
    } else if (page > pages) {
      $wrapper.scrollLeft(singleWidth * visible);
      page = 1;
    } 

    currentPage = page;
  });                
  
  return false;
}

Navigation buttons

Finally we need to add the buttons or links to page through the carousel and bind the gotoPage call.

We’re also going to create a custom goto event that allows us to also jump to any page:

$wrapper.after('<a class="arrow back">&lt;</a><a class="arrow forward">&gt;</a>');

$('a.back', this).click(function () {
    return gotoPage(currentPage - 1);                
});

$('a.forward', this).click(function () {
    return gotoPage(currentPage + 1);
});

// this currently refers to the element the plugin was bound to
$(this).bind('goto', function (event, page) {
    gotoPage(page);
});

Demo

That’s it. It’s a fairly complicated plugin as J4D demos go. There is also the jCarousel plugin that you could use (though I’m not sure if it has infinite scrolling) - but obviously the idea here is to show you how it all comes together.

Check out the demo, view the source, have a play!

Related posts

Demo

If you find this demo doesn't work as expected, it's possibly due to the demo running from within an iframe. Try running the demo in it's own window.

Source Code

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Infinite Carousel</title>
<style type="text/css" media="screen">
<!--
body { font: 1em "Trebuchet MS", verdana, arial, sans-serif; font-size: 100%; }
input, textarea { font-family: Arial; font-size: 125%; padding: 7px; }
label { display: block; } 

.infiniteCarousel {
  width: 395px;
  position: relative;
}

.infiniteCarousel .wrapper {
  width: 315px; /* .infiniteCarousel width - (.wrapper margin-left + .wrapper margin-right) */
  overflow: auto;
  min-height: 10em;
  margin: 0 40px;
  position: absolute;
  top: 0;
}

.infiniteCarousel ul a img {
  border: 5px solid #000;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
}

.infiniteCarousel .wrapper ul {
  width: 9999px;
  list-style-image:none;
  list-style-position:outside;
  list-style-type:none;
  margin:0;
  padding:0;
  position: absolute;
  top: 0;
}

.infiniteCarousel ul li {
  display:block;
  float:left;
  padding: 10px;
  height: 85px;
  width: 85px;
}

.infiniteCarousel ul li a img {
  display:block;
}

.infiniteCarousel .arrow {
  display: block;
  height: 36px;
  width: 37px;
  background: url(images/arrow.png) no-repeat 0 0;
  text-indent: -999px;
  position: absolute;
  top: 37px;
  cursor: pointer;
}

.infiniteCarousel .forward {
  background-position: 0 0;
  right: 0;
}

.infiniteCarousel .back {
  background-position: 0 -72px;
  left: 0;
}

.infiniteCarousel .forward:hover {
  background-position: 0 -36px;
}

.infiniteCarousel .back:hover {
  background-position: 0 -108px;
}



-->
</style>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>


<script type="text/javascript">

$.fn.infiniteCarousel = function () {

    function repeat(str, num) {
        return new Array( num + 1 ).join( str );
    }
  
    return this.each(function () {
        var $wrapper = $('> div', this).css('overflow', 'hidden'),
            $slider = $wrapper.find('> ul'),
            $items = $slider.find('> li'),
            $single = $items.filter(':first'),
            
            singleWidth = $single.outerWidth(), 
            visible = Math.ceil($wrapper.innerWidth() / singleWidth), // note: doesn't include padding or border
            currentPage = 1,
            pages = Math.ceil($items.length / visible);            


        // 1. Pad so that 'visible' number will always be seen, otherwise create empty items
        if (($items.length % visible) != 0) {
            $slider.append(repeat('<li class="empty" />', visible - ($items.length % visible)));
            $items = $slider.find('> li');
        }

        // 2. Top and tail the list with 'visible' number of items, top has the last section, and tail has the first
        $items.filter(':first').before($items.slice(- visible).clone().addClass('cloned'));
        $items.filter(':last').after($items.slice(0, visible).clone().addClass('cloned'));
        $items = $slider.find('> li'); // reselect
        
        // 3. Set the left position to the first 'real' item
        $wrapper.scrollLeft(singleWidth * visible);
        
        // 4. paging function
        function gotoPage(page) {
            var dir = page < currentPage ? -1 : 1,
                n = Math.abs(currentPage - page),
                left = singleWidth * dir * visible * n;
            
            $wrapper.filter(':not(:animated)').animate({
                scrollLeft : '+=' + left
            }, 500, function () {
                if (page == 0) {
                    $wrapper.scrollLeft(singleWidth * visible * pages);
                    page = pages;
                } else if (page > pages) {
                    $wrapper.scrollLeft(singleWidth * visible);
                    // reset back to start position
                    page = 1;
                } 

                currentPage = page;
            });                
            
            return false;
        }
        
        $wrapper.after('<a class="arrow back">&lt;</a><a class="arrow forward">&gt;</a>');
        
        // 5. Bind to the forward and back buttons
        $('a.back', this).click(function () {
            return gotoPage(currentPage - 1);                
        });
        
        $('a.forward', this).click(function () {
            return gotoPage(currentPage + 1);
        });
        
        // create a public interface to move to a specific page
        $(this).bind('goto', function (event, page) {
            gotoPage(page);
        });
    });  
};

$(document).ready(function () {
  $('.infiniteCarousel').infiniteCarousel();
});
</script>
</head>
<body>
    <h1>Infinite Carousel</h1>
    
    <div class="infiniteCarousel">
      <div class="wrapper">
        <ul>
          <li><a href="http://www.flickr.com/photos/remysharp/3047035327/" title="Tall Glow"><img src="http://farm4.static.flickr.com/3011/3047035327_ca12fb2397_s.jpg" height="75" width="75" alt="Tall Glow" /></a></li>
          <li><a href="http://www.flickr.com/photos/remysharp/3047872076/" title="Wet Cab"><img src="http://farm4.static.flickr.com/3184/3047872076_61a511a04b_s.jpg" height="75" width="75" alt="Wet Cab" /></a></li>
          <li><a href="http://www.flickr.com/photos/remysharp/3047871878/" title="Rockefella"><img src="http://farm4.static.flickr.com/3048/3047871878_84bfacbd35_s.jpg" height="75" width="75" alt="Rockefella" /></a></li>
          <li><a href="http://www.flickr.com/photos/remysharp/3047034929/" title="Chrysler Reflect"><img src="http://farm4.static.flickr.com/3220/3047034929_97eaf50ea3_s.jpg" height="75" width="75" alt="Chrysler Reflect" /></a></li>
          <li><a href="http://www.flickr.com/photos/remysharp/3047871624/" title="Chrysler Up"><img src="http://farm4.static.flickr.com/3164/3047871624_2cacca4684_s.jpg" height="75" width="75" alt="Chrysler Up" /></a></li>
          <li><a href="http://www.flickr.com/photos/remysharp/3047034661/" title="Time Square Awe"><img src="http://farm4.static.flickr.com/3212/3047034661_f96548965e_s.jpg" height="75" width="75" alt="Time Square Awe" /></a></li>
          <li><a href="http://www.flickr.com/photos/remysharp/3047034531/" title="Wonky Buildings"><img src="http://farm4.static.flickr.com/3022/3047034531_9c74359401_s.jpg" height="75" width="75" alt="Wonky Buildings" /></a></li>
          <li><a href="http://www.flickr.com/photos/remysharp/3047034451/" title="Leaves of Fall"><img src="http://farm4.static.flickr.com/3199/3047034451_121c93386f_s.jpg" height="75" width="75" alt="Leaves of Fall" /></a></li>
        </ul>        
      </div>
    </div>
</body>
</html>

Comments

  1. Fannyfu On 15th October 2009 at 21:10

    Awesome plugin! Thanx for it. Just a question, why don’t you change in the source code the line “min-height” for just “height” if now we know that this way it will work fine in IE too? This change would save us time. I’m not fan of IE, I’m happy if everything works fine in FF or Chrome, but the final users are IE users and we work for them. Thanx again. :-)

  2. rfathoni On 17th October 2009 at 03:10

    same with Carlos, it does not work over ie6, but i try to delete position: absolute; on .infiniteCarousel .wrapper ul {} and also delete posisition:abosulete; on .infiniteCarousel .wrapper and now works perfect on ie6. Thx Carlos and Remy

  3. Brian On 22nd October 2009 at 12:10

    Hey,

    I was wondering if there was a way for me to add two “Infinite Carousel’s on one page? I have tried making separate pages inside one page using two different php files, but that isn’t working. I have also tried editing the js/css by doublicating the coding and just placing a “2″ behind each part of code such as “infiniteCarousel2″ and the wrapper as well, but that as well does not seem to fix my problem.

    Hopefully you can be of assistance to me.

    Thanks for your time, Brian W

  4. Danny On 24th October 2009 at 00:10

    I want a carousel that works with the following requirements:

    1. Each image or element that gets scrolled has a variable width/height. The dimensions could be specific inline via width/height attribute or inline CSS though, that is fine.

    2. I want the script to show as many “slides” as will fit the container at any given time. Other scripts required you set a specific number of slides to be shown at all times. This doesn’t work for me. It’s fine with one of the slides is cut off sometimes or all of the time though.

    3. I need it to autoscroll, and also have prev/next links.

    Can this do that? Would anything need to be customized?

    I have tried so many already… an answer would be helpful so I don’t have to experiment for more hours.

    Thanks

  5. gabriel On 26th October 2009 at 22:10

    Hi, there. I just realize that this slider it doesnt work for IE6. The solution is to add just a line in the CSS that comes with it. In the line that says ” .infiniteCarousel .wrapper” you need to add a fixed height in order for the images (or the ul) to appear. In my case i added ” height:100px; “. BTW, it works perfectly! Also, i would like to thank you for sharing this amazing plugin.

    Cheers!

  6. sharq On 2nd November 2009 at 11:11

    Hi. Could someone tell me how to use this goto page function? What should I put into html? I’d like to have something like: 1 2 Regards

  7. brando On 3rd November 2009 at 14:11

    if anyone posts how to setup multiple instances of infinite carousels - i will post how to make it vertical ;)

  8. TS On 4th November 2009 at 09:11

    Is there a way I could easily configure this code so that it would load the images in a div container above the carousel? If someone could assist me with this, I would really appreciate it.

    Thank You TS

  9. George On 13th November 2009 at 03:11

    Awesome plugin! But can this be linked into the jQuery Cycle plugin?

    I am having trouble doing this (it works ALMOST).

  10. Brian Schroeder On 22nd November 2009 at 01:11

    Remy, thanks for the tutorial. I was seeing how this works when nested inside a div that can be toggled. When that div is toggled and comes back up, the carousel automatically jumps to the last li, no matter where it was in the carousel. For user-friendliness, I’d like it to stay where they had it when the div was toggled. Any clue why it would do this, or a fix? Thanks!

  11. Alan Horne On 25th November 2009 at 15:11

    Hi there,

    i’m working with javascript fr the first time and i have been trying to implement your carousel onto a website im working on.

    Now on my test page i have everything working fine : http://www.itsfilmtastic.co.uk/airportparkandride/carouseltest.html

    But when i implement the code on to my site, the background for the containing DIV doesn’t want to repeat for some reason, as well as the buttons not showing, although still working. http://www.itsfilmtastic.co.uk/airportparkandride/index.html

    Would you be able to advise me on what i should be correcting on the code?

    To me it looks like the positions on the arrows are what is causing the problems.

    Thanks Alan

  12. Alan Horne On 27th November 2009 at 10:11

    Hi there, i managed to fix my above problems, i needed to change .infiniteCarousel .wrapper ul to position:relative, and another one i can’t remember.

    For the images, its the path that needed changed, when implemented on to the site.

    Works great now, thanks very much.

  13. Filmore Ha On 30th November 2009 at 03:11

    Awesome! Thanks for this awesome plugin. I just finished implementing it in a Drupal site and it works flawlessly. I did have to change two things though. As mentioned above, I also had to add a height declaration to get it to work in IE. I also went ahead and threw in the “height: auto !important;” declaration for good measure. Also I changed the “position: absolute;” declaration to relative under .infiniteCarousel .wrapper

    Works a charm. Cheers!

  14. kali On 1st December 2009 at 23:12

    Great plugin! I have enjoyed playing around with it a bit. I’ve tried using a swap image behavior, and it works fine on the images that are visible initally, but doesn’t function on the others that aren’t. Any thoughts on why that might be happening and how I might fix it?

    Thanks a lot!

  15. Keegan On 8th December 2009 at 11:12

    Your scroller seemed great so I implemented it on my site but when doing my browser checking I found out that it doesn’t work (at least out-of-the-box) with IE6. I’m not a fan of IE6 like most people, but all serious web developers still have to support it as it still has more users than Chrome or the less common browsers.

    Argh, now I’m off to find another one to do the job.

  16. MikeW On 30th December 2009 at 20:12

    Are there any resources out there to help with implementing this on a wordpress theme?

    I’ve looked everywhere for a similar wordpress plugin or tutorial on how to achieve this in a theme template but with no luck.

    A point in the right direction would be much appreciated.

    Cheers,

    Mike.

  17. Nathan On 16th January 2010 at 00:01

    Hi, really nice effect but I am having an odd problem in my implementation. Once the carousel is done scrolling, rather than resetting it continues on to scroll into oblivion. You can see it here http://bit.ly/17Mau6 thanks in advance to anyone who has an answer for this.

    Also the only things I have edited in the css are the size of the carousel and the images that are linked for the arrows.

  18. webb On 16th January 2010 at 04:01

    This is the best carousel i have seen so far, i am definitely bookmarking this page.

  19. Jon On 11th March 2010 at 17:03

    Firstly this looks fantastic and I am implementing into a current project. I have successfully added the function and added auto scroll to but instead of scroll on 3 it scroll on 4 so it starts off by showing 1-3 then scrolls and shows 5-6.

    Is there any reason for this. I have increased the width to: .infiniteCarousel { width: 890px; position: relative; }

    .infiniteCarousel .wrapper { width: 790px; /* .infiniteCarousel width - (.wrapper margin-left + .wrapper margin-right) */ overflow: auto; height: 105px; margin: 0 50px; position: absolute; top: 0; }

    .infiniteCarousel ul a img {

    -moz-border-radius: 5px; -webkit-border-radius: 5px; }

    .infiniteCarousel .wrapper ul { width: 1398px; /* single item * n */ list-style-image:none; list-style-position:outside; list-style-type:none; margin:0; padding:0; position: absolute; top: 0; }

    .infiniteCarousel ul li { display:block; float:left; padding: 10px; height: 109px; width: 243px; }

Leave your own comment
  • http://