Articles

Variable Width Slideshow

Recently I was asked by a designer to create a slideshow where the images are of variable width, each image needs to center in the frame of the slideshow, and the control panels on the left and right need to animate to adjust for each image. I think this creates an attractive, sleek effect and could be quite usable by other folks, so turning it into a jQuery plugin is the next step…but for now, here’s an explanation of how I created it. First, check out the final version:

Next Picture

Previous Picture

Pause

Play

Previous Image

Next Image

Current image description

Right. So, before we get into any jQuery fun, we need to do a little markup and styling. However you can view the complete script here if you like. First, let’s put down some markup for the nav:

Looking at this markup, my first thought is, that’s quite a few id’s and classes, more than you’d like to see. However, because of the functionality of the slideshow, it helps to have a hook for each list-item in the nav. We could use jQuery to target the play and pause list items without id’s, but that means more javascript, so I think it’s a reasonable trade-off for the functionality.

Pretty simple stuff. I added a width to the index-display because if it is set to auto, when the numbers change, the difference in character widths will change the width of the list item, and the items to the left will jump a bit to the right or left as the character widths change. So setting a fixed width for it takes care that issue. However, we do have an accessibility issue to deal with. The list-item for the image counter is empty, so we need to figure out how to get some semantic content in there. I made the following changes:

There may be a more accessible way to do this, however, I’m going to leave that to a future update. For now, there’s a meaningful term within the list item, it doesn’t break the layout and it can be accessed by a screenreader.

Next thing to do is set up some markup for the body of the slideshow. Here is the structure:

Again, simple CSS. One thing to notice, in that in the declarations for #mycarousel li, I haven’t floated the list items to the left. I’d rather they display vertically if Javascript is turned off. It’s more graceful to have to scroll down rather than across…so those list items will be floated using jQuery. I’m using positioning contexts to place the left and right panels in the left top and right top corners. Also, the ul #mycarousel is position:relative as well. This is because we are going to be calculating left offset values for how much the this unordered list needs to move based on the width of the image adjacent to the one centered in the viewport .

The last piece of markup is the container that will display the alt attribute information for each image. Again, we’ll add a span to it for accessibility, using display:none; as our style declaration.

<p id="display"><span>Current image description</span></p>

Right! That about does it for the markup and styles. Now let’s take a look at the jQuery that makes the magic happen. Here’s the first block of code:

There are different methods to get jQuery to work when there’s conflict with another library (such as prototype or scriptaculous.) The first line of code has worked well for me on WordPress sites, while the shorthand version ($function() {}); almost always gives me a problem. There are couple of other things to notice up front. The first line floats the li’s (containing the images) left, so that without javascript they’ll fall vertically as we mentioned before. Same concept is in place for #product-slideshow-inside. We can’t set the overflow:hidden rule in the CSS otherwise with javscript off, you would only see that first image displayed.

Next we start organizing the slideshow’s logic. We assign a variable “n” to a number which equals the number of list items that are contained within the unordered list #mycarousel. Then we subtract one from that number because of a slight quirk in the markup. When you navigate to the last image, it doesn’t look great if there is a blank panel to the right, so I’ve just added the first image again to the end of the list. This is not very semantic, and in the next version I should append that extra image using jQuery, but for now, we subtract one from the variable to indicate the unique number of images. This number is then appended to #index-display so the user sees the total number of unique images available. The next task is to set the total width of the unordered list. We want the unordered list to be the same width as total width of all list items, so, we use the each() command to add the width of each list item to the variable parentwidth. Finally, we use the value of parentwidth to set the width of #mycarousel. Let’s move on to the next block:

Ok. So, we are going to need to set some rules to determine how the slideshow will display when the page is first loaded. This portion uses some very simple math meant to center the first image and slide panels properly. We take the width of the entire slideshow, subtract the width of the first list item, divide that by two and assign it to a variable. We're dividing by two because after we subtract the width of the list item, the remainder will be split evenly between the left and right panel. We then move the entire unordered list (#mycarousel) to the right by that same amount. Finally, we grab the alt attribute from image within the list item (by using the children() command) and prepend it to the #display paragraph under the slideshow.

This next block of code causes the black arrows in each panel to either display or hide, depending on the state of the slideshow:

But Jon, you say, what's the deal with the currentPosition variable? Where is that coming from? Good question. It's a global variable that I actually declare further on in the script and its value is set to 1. So that being the case, when you hover on the slideshow as soon as it loads, the left panel's button will not show. I have used the very well known pixy technique here to hide and show button images. However if currentPosition is not equal to 1, then hovering over the slideshow will cause both arrows to be visible. Now, the next bit is where it starts to get interesting...

So here we've created an object (Sliderules) and a method for the object (slideactions). The method is a function that represents a block of commands that are common to both sets of slideshow controls - the red buttons up top as well as the right and left panels. When you click on a navigation panel or button, or when the slideshow runs automatically, there is a function call. The first thing that happens inside that function is that the value of currentPosition gets incremented by 1. After this happens, the slideactions() method is then called. Since the currentPosition value has now been incremented, the calculations here are being made on the image to the right of the centered image, before it moves into the viewport. So, the currentItem variable represents the next image (currentPosition-1, to offset for the ordinal index). The x-y position values of the currentItem are assigned to itemLocation, and the leftdistance variable is assigned the left offset value. These offset numbers are all relative to the containing div, since it was set to position:relative. Next, we subtract the width of currentItem from the container width, and divide that by two to determine how wide each panel will need to be, round that number off, and set both panels to animate to that width. We need to figure out exactly how far #mycarousel has to move, which we can do by taking the value of leftdistance (how far the current item is from the left) and subtracting the width of one of the panels (which we just calculated for panelOffset). Then we make that figure negative, so #mycarousel will be pulled to the left the proper distance. Finally, we add the image caption. In this next block we'll see how this function is called.

So here is where I have declared var currentPosition=1. So currentPosition is a global variable, meaning that when I use it locally inside a function, because it isn't defined, it will go up the scope chain and change the global variable's value. I'm aware that this isn't necessarily the best way to operate, but here I think it makes sense. Ultimately I want to be able to change the global value of currentPosition from three different functions, so that the right position will be maintained no matter which slide controls you use. So here you can see the global currentPosition variable being incremented. Also we have an if statement which says if the value of currentPosition is higher than the number of items, go back to item number 1. Then we call the slideactions method of the Sliderules object. So this makes the slideshow start automatically and change images every 6 seconds if you don't do anything. Below that, the interval is cleared if any of the controls are clicked. Finally, here's the last piece...

Ok! Not too bad! So here, we are writing the logic for the slide panels and buttons. First, we get the length and assign it to var n. Next, make sure the play and pause button colors are dark grey. Then an or statement which either increments or decrements 1 from the value of currentPosition (so you can go back in the slideshow). Finally, we add a little touch in at the end that will display the left arrow once you are past the first image...this is a small embellishment and acts a visual cue. Finally, we call our good old slideactions() method, and then go have a cup of tea. I'm sure this script has room for improvement! Let me know what you think...

Hey, thanks for the comment. You can go ahead and use this in production…it would be great if you left a link, but you don’t have to. As far as your other requests…if you wanted a 100% width on the slideshow, I’d try just setting the width on the parent and inner containers to 100%, which should just set it to the width of whatever the parent element is. I don’t see why it wouldn’t work. As far as the infinite slide, that’s a useful idea, I’ll post back here if I come up with something. If you do use it, send me a link, I’d love to see it in action.

You’re right, it would be probably be sharper to be able to go back and forth. There’s a gap to the left which I can see wouldn’t look great if the slideshow was very wide. One option might be to put a background color, or background image (like x’s or repeating lines) on the parent slideshow element. That way it would be clear that there was no image to the left and it would be covered as soon as the slideshow moved. It’s a short term solution but like I said, I may play around with that….possibly later in the week.