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

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:

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:
- Markup that works without JavaScript (done!)
- Initialisation & capture the height and width of the first carousel item (required for calculations)
- Create empty list items to pad to fill every page
- Generate cloned items on the start and end of the list
- Adjust the scroll left position to show the real first item
- Create goto method that supports forward and backwards
- 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:
- jQuery instance of the overflow element
- Hiding the overflow
- jQuery instance of the list
- jQuery instance of the list items
- The first item and it’s width
- Number of visible items we can fit in the carousel (rounded up via
Math.ceil) - 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:
- Direction we’re going to slide
- Number of pages to slide
- 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"><</a><a class="arrow forward">></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!
You should follow me on Twitter here I tweet about jQuery amongst usual the tweet-splurges!
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"><</a><a class="arrow forward">></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>

Play QuickTime version
Play Flash version
Yatrik Vyas On 11th June 2009 at 15:06
Hey, this is what I was searching here and you just posted.
So the first Thank You from me.
Thank’s for posting!
Aric On 11th June 2009 at 16:06
Awesome tutorial. I have been looking for something like this.
Great work
Anjei On 11th June 2009 at 16:06
finally you returned ! excellent lesson thank you
vik On 11th June 2009 at 18:06
It’s wonderful!
webmasterdubai On 11th June 2009 at 18:06
good JQuery plugin really good thing is infinite carousel.
Chris Greenhough On 11th June 2009 at 20:06
Welcome back, Remy.
HB On 11th June 2009 at 22:06
Very cool tutorial, the pics do a good job of describing how the clone is built and used. I used pretty much the same method on a vertically scrolling news widget, resetting the vertial position once the clone reached the top.
Eric On 12th June 2009 at 03:06
Thank you. wonderful. I always ready to watch your every tutorial
Daniel On 12th June 2009 at 06:06
Beautiful as always.
Thomas Svensson On 12th June 2009 at 07:06
Thank you so much Remy for sharing your jQuery knowledge! Screencasts is a great way to learn and your screencasts are really great. If I only could say one ting, sometimes it’s hard to follow your variables and what value that they contains. Thanks again!
PS! Your talk about Unobtrusive Web Development with Chris Mills on Geek Meet Stockholm was great as well! Hope you visit Sweden again soon.
Tony Mandigo On 12th June 2009 at 09:06
great tutorial!!!
how can i automate this example so that it scrolls automatically?
Myfacefriends On 12th June 2009 at 14:06
thanks for your wonderful infinite Carousel., i will try this to insert in my site.
Patrick On 12th June 2009 at 14:06
Great! Thank you, I was thinking about doing something like this, but could’t figure out how to go to the real first items. Now I do!
GN On 12th June 2009 at 16:06
Cool tutorial, although keep in mind that the plural of “it” is “its.” You may dismiss as nitpicking, but attention to detail is important in design and programming, it should be in your writing as well. I am very skeptical to apply to advise of someone who makes junior-high mistakes.
Carl On 12th June 2009 at 22:06
Great tutorial….i think i got lost somewhere around the middle, but seeing this sort of work always inspires me to actually learn. it’s cool that you wear your influences on your sleeve… Thanks!
Dom On 13th June 2009 at 10:06
Thanks for this. “Serendipitously” it’s exactly the kind of problem I’m just trying to solve at the moment and this video and post are a huge life saver, thank you.
d
Remy On 13th June 2009 at 10:06
@Thomas Svensson - really glad you enjoyed the session with Chris - we both had a ball of a time. I’ll be back in Sweden for Øredev in November for more jQuery love!
@GN - absolutely agree (with the spelling part). Sadly the state of education in the UK sucks (for English grammar), or certainly sucked for me. Ultimately I’m a programmer - maths brain - and as I don’t have an editor for this site, I have to sometimes rely on you guys and gals to point out the errors. I’ll go back through and fix the typos.
As for this:
Not sure I agree with that principle, but hey, you’re entitled to ignore my code ;-)
Dave Redfern On 13th June 2009 at 12:06
Hello,
Nice tutorial although there are plugins which accomplish this a little better than your demo. Your demo shows a gap at the end but both jcarousel (mentioned in post) and jcarousel lite offer a circular carousel which is continuous with no gaps. This means that it appears seamless rather than have a gap at the end if there isn’t the correct number.
of course the advantage with yours is it accomplishes it in considerably less amount of code to both these plugins.
keep up the good work.
dave.
Jake Rutter On 13th June 2009 at 14:06
Great Job! Im surprised this wasnt built into jCarousel, it seems like the basic premise of a carousel would be to go round and round!
P.S. - It works great on FF, not as great in some instances on IE7.
Chris Greenhough On 13th June 2009 at 16:06
@Remy, well, if you’re looking for someone to provide editorial QA for the site… I volunteer! And please, p-l-e-a-s-e, can you add some kind of subscribe to comments facility? I love your site (as you know by now) but it drives me crazy when a valuable and popular information source like this does not let you know when someone follows up on a comment thread you have contributed to. Makes the flow of conversation difficult, n’est ce pas? And Smashing Magazine is guilty of the same thing. I know, you’re probably thinking we’re all savvy enough to subscribe to the comments rss feed, right? Well, some of us aren’t! (or just prefer old-fashioned email notifications because it makes us feel wanted).
And by the way, @GN, speaking of high-school mistakes… You take someone’s advice, not advise. One is a noun, the other is a verb. And what you are highlighting is not a plural of it but a possessive, as in belonging to, although you are correct that an apostrophe is not required, and is one of those rules that people struggle to get their head around…
Remy On 13th June 2009 at 16:06
@Chris - cheers for the feedback. The link for the comments RSS is at the bottom of the page (well - every page), under the feeds category. It was always there since the redesign - hope that solves that one for you! :-D
I’m going to be adding the “all posts”/the archive type link to the top when I’ve got some spare time along with a decent intro tucked away in the header of this site - so that should quell the main request on the redesign people have had.
Cheers - Remy.
danielre On 14th June 2009 at 11:06
Great tutorial. Thank you.
Is there a comfortable way of changing the images, so that even a person with no coding skills can change them?
Thank you in advance.
tomoe On 14th June 2009 at 11:06
Hi. Really great tutorial ! Thanks man ! Do you think it’s possible to make this work with ie6 / 7 ?
Jim On 15th June 2009 at 03:06
Very nice and thorough tutorial. I am looking for a way to automate the navigation of the slider on a timed basis; is that feature available in this lib?
Thanks!
strazi On 15th June 2009 at 14:06
For IE6, you can change the “min-height” in the css for the wrapper to just “height” and it seems to work fine.