Improving jQuery Infinite Carousel (part one)

Recently i was looking for a jQuery Carousel and the most interesting I found was the one proposed by jQuery For Designers, but it has a lot of limitations that I would bypass because I’d like to use the carousel in a full block context and second because I want use the “Swipe” touch gesture to scroll the carousel (tablet context).  Let’s see how I’ve solved them and improved the Carousel!

Issue #1: “fixed carousel width should be flexible to the width of the container”

In the original file (read the source code), you can observe that the .infiniteCarousel class properties sets the width to 395 pixels, and the contained item to 315 pixels:

.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;
}

To make the carousel flexible, I’ve changed those two class properties into the new ones:

.infiniteCarousel {
  min-width: 395px; /* now this is the minimal allowed width */
  width: 100%; /* adapt the width to the container */
  position: relative;
}

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

Then I made a minor change to the “.infiniteCarousel .arrow” CSS rule, because a less than symbol (“<“) appears in the middle of my screen.

text-indent: -9999px;

With the above code we have solved the first issue. But now we have introduced a new issue. You can notice it only if you are looking at the new carousel through a tablet browser. Which one? Let’s rotate your tablet by 90° (obiviously if your tablet has the rotation sensor!!! :) ) and take a look to the carousel. You’ve seen the breaking issue? No? Try to use the left and right arrow buttons of the carousel!

What? Which one of you told me that the issue is in the desktop browser window resizing too? Yes I know! We have to solve that first.

Issue #2 (raised by resolution of Issue #1): “Window resizing/tablet rotation causes the carousel to not work properly”

Supposing that you have understand how the original infinite Carousel works (there’s no better read than take a look to the original post)  let’s alter the javascript.

The first thing we need to handle is the resize event of the window and keep the carousel object context we create to recalculate some information that changes during resize. So, let’s create the handler into the “infiniteCarousel” function.

$(window).resize(recalculateAfterResize);

but what need to be calculated after the resize event?

  1. the width of visible area;
  2. the number of pages which the carousel has;
  3. the empty items;
  4. the cloned items;

Well so the first thing we have in that function will be the removal of the nodes with class .cloned and .empty to ensure to have a clean (original) condition.

$('.empty', $wrapper).remove();
$('.cloned', $wrapper).remove();

After that we perform the tasks #1 and #2 of the above list.

$items = $slider.find('> li');
$wrapper.visible = Math.ceil($wrapper.innerWidth() / singleWidth), // note: doesn't include padding or border
$wrapper.pages = Math.ceil($items.length / $wrapper.visible);

Let’s count the number of empty items to create.

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

Clone the required items:

// 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(- $wrapper.visible).clone().addClass('cloned'));
$items.filter(':last').after($items.slice(0, $wrapper.visible).clone().addClass('cloned'));

Then reset the scroll to the first available item of the carousel:

$items = $slider.find('> li'); // reselect
$wrapper.scrollLeft(singleWidth * $wrapper.visible);
page = 1

At this point we should have a method like that:

function recalculateAfterResize(){
    // Reset to the original carousel condition
    $('.empty', $wrapper).remove();
    $('.cloned', $wrapper).remove();
    $items = $slider.find('> li');
    $wrapper.visible = Math.ceil($wrapper.innerWidth() / singleWidth), // note: doesn't include padding or border
    $wrapper.pages = Math.ceil($items.length / $wrapper.visible);
    // 1. Pad so that 'visible' number will always be seen, otherwise create empty items
    if (($items.length % $wrapper.visible) != 0) {
        $slider.append(repeat('<li />', $wrapper.visible - ($items.length % $wrapper.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(- $wrapper.visible).clone().addClass('cloned'));
    $items.filter(':last').after($items.slice(0, $wrapper.visible).clone().addClass('cloned'));
    $items = $slider.find('> li'); // reselect
    $wrapper.scrollLeft(singleWidth * $wrapper.visible);
    page = 1;
}

Just put it after the declaration section inside the “return this.each” function and remove the following variables from declaration section:

  • visible
  • pages

The last thing to do is to remove the redundant code (all the code identified by the comments //1. … , //2. … and //3. … ) then substitute the removed blocks with a single call to the above function recalculateAfterResize();

For a cleaner reading I have separated the code in three files: CSS, Javascript and HTML. You can find them in the example file or look at the demo that I just made for you!

In the next article i will describe how manage the swipe effect in the simplest way and improve some other minor things in the Carousel! Stay tuned. And happy Coding!