How to Solve the Problem
Recreating this effect is simple to do if you know what plugins to use. There are plugins out in the wild already, but we want our jQuery to satisfy the following requirements:
- Degrades perfectly without JavaScript enabled
- Sliding panels effect without hogging browser CPU
- Next and previous buttons added using JavaScript because they hold no use without JavaScript
- Hitting the page with a specific hash (i.e. page.html#preview) shows the right tab, and highlights the right navigation
- Any link on the page that refers back to a panel should trigger the effect and highlight the right navigation - this should happen without any extra work
The hash is the emphasised part (including the # symbol) in: http://jqueryfordesigners.com/page.html#example
In fact, our version of this slider will be better than Panic’s and the current jQuery plugins if we can meet all of the requirements.
For example, in the Panic example linking directly to the preview pane doesn’t correctly highlight the navigation.
This whole solution is going to rely heavily on Ariel Flesler’s scrollTo plugin and particularly it’s ‘adapters’ to enhance the power of the scrollTo plugin.
I’ve provided a screencast to walk through how create this functionality. Details on how and what I used can be found below.
Watch the coda slider effect screencast (alternative flash version)
QuickTime version is approx. 90Mb, flash version is streaming - I’ve also noticed the sound is slightly over to the left - I’ll fix that for next time!)
View the demo and source code used in the screencast
Plugins Required
Along with jQuery we’ll need the following plugins - I recommend using the minified versions for production, and perhaps even development (download links can be found at the bottom of the linked pages):
Markup
Requirement 1: degrades perfectly without JavaScript enabled
To ensure this works without JavaScript, we will use the CSS overflow property to control the effect.
This way our panels will still focus in to view if a user clicks, or if the URL refers to a specific panel.
HTML
It’s worth noting that although we could scroll the individual panels inside a single nested DIV, for the JavaScript to make the effect work, it will be a nested DIV that will scroll - not the individual panels. You can see this in the markup below:
<div id="slider">
<ul class="navigation">
<li><a href="#sites">Sites</a></li>
<li><a href="#files">Files</a></li>
<li><a href="#editor">Editor</a></li>
<li><a href="#preview">Preview</a></li>
<li><a href="#css">CSS</a></li>
<li><a href="#terminal">Terminal</a></li>
<li><a href="#books">Books</a></li>
</ul>
<!-- element with overflow applied -->
<div class="scroll">
<!-- the element that will be scrolled during the effect -->
<div class="scrollContainer">
<!-- our individual panels -->
<div class="panel" id="sites"> ... </div>
<div class="panel" id="files"> ... </div>
<div class="panel" id="editor"> ... </div>
<div class="panel" id="preview"> ... </div>
<div class="panel" id="css"> ... </div>
<div class="panel" id="terminal"> ... </div>
<div class="panel" id="books"> ... </div>
</div>
</div>
</div>
That’s it. Without CSS this markup works perfectly for our content, almost exactly the way tabs work without JavaScript and CSS.
CSS
I won’t detail all the CSS like the navigation or shading effects - just the CSS required to correctly create the effect.
#slider {
width: 620px;
margin: 0 auto;
position: relative;
}
.scroll {
height: 250px;
overflow: auto;
position: relative; /* fix for IE to respect overflow */
clear: left;
background: #FFFFFF url(images/content_pane-gradient.gif) repeat-x scroll left bottom;
}
.scrollContainer div.panel {
padding: 20px;
height: 210px;
width: 580px; /* change to 560px if not using JS to remove rh.scroll */
}
Note also that I’m choosing to use overflow: auto; - this way, if the user does have JavaScript disabled, there will be a visual que that the panels can be scrolled. If you want to remove the horizontal scroll in IE, I would recommend changing .scrollContainer div.panel’s width to 560px to account for the right hand scrollbar.
I also plan to include scrolling buttons to go left and right. Although the elements will be created by the JavaScript, we’ll still need the CSS to place the buttons in the right place. Our JavaScript will put the buttons before and after the outer div.scroll element. We have to use absolute positioning to place them, so this is why #slider has position: relative; applied:
.scrollButtons {
position: absolute;
top: 150px;
cursor: pointer;
}
.scrollButtons.left {
left: -20px;
}
.scrollButtons.right {
right: -20px;
}
jQuery
Requirement 2: Sliding panels effect without hogging browser CPU
If you try this effect using CSS, to use positioning to move the panels around it will eat up your CPU, and the browser will no likely lock up. This is why we’re using Ariel’s scrollTo pluing. The scrollTo plugin literally scrolls the element via it’s overflow (or can be used to scroll the actual window).
Required Plugins
<script src="jquery-1.2.6.min.js" type="text/javascript"></script>
<script src="jquery.scrollTo-1.3.3-min.js" type="text/javascript"></script>
<script src="jquery.localscroll-1.2.5-min.js" type="text/javascript"></script>
<script src="jquery.serialScroll-1.2.1-min.js" type="text/javascript"></script>
Next and Previous Buttons
Requirement 3: Back and forward buttons added
This is easy vanilla jQuery, here’s the snippet from the final code:
var $scroll = $('#slider .scroll');
$scroll
.before('<img class="scrollButtons left" src="images/scroll_left.png" />')
.after('<img class="scrollButtons right" src="images/scroll_right.png" />');
Note the classes I’ve applied the scrollButtons to the images so they’ll be positioned properly.
Navigation Updates Automatically
Requirement 4: Hitting the page with a specific hash highlights the right navigation
This is one of the key features that I felt was important to the slider. If I navigated to a specific panel, the navigation should match.
This is achieved using event binding. When the scroll plugin completes it’s effect, it will call our trigger function.
The trigger function will receive the ID of the element it has just shown, and search for the navigation item whose href’s hash matches the ID.
For example, if the div#sites element is shown, the ID ’sites’ is sent to our trigger. Our trigger function will now look for links in the navigation that end with ‘#sites’: <a href="#sites">Sites</a>.
This trigger function will be attached to the onAfter property in the scroll options and called when the page loads for the first time with a hash in the URL.
// bind the navigation clicks to update the selected nav:
$('#slider .navigation').find('a').click(selectNav);
// handle nav selection - lots of nice chaining :-)
function selectNav() {
$(this)
.parents('ul:first') // find the first UL parent
.find('a') // find all the A elements
.removeClass('selected') // remove from all
.end() // go back to all A elements
.end() // go back to 'this' element
.addClass('selected');
}
function trigger(data) {
// within the .navigation element, find the A element
// whose href ends with ID ($= is ends with)
var el = $('#slider .navigation').find('a[href$="' + data.id + '"]').get(0);
// we're passing the actual element, and not the jQuery instance.
selectNav.call(el);
}
I’ve had to separate out the selectNav function because it also be called when the user clicks on the navigation links.
To complete this requirement, once the page is loaded, we need to check whether there is a hash on the URL, and if so, trigger the ’scrollerComplete’ function:
if (window.location.hash) {
trigger({ id : window.location.hash.substr(1)});
} else {
$('#slider .navigation a:first').click();
}
By default, we’re triggering a click to the first navigation item if there’s no hash on the URL.
Any Link Triggers Effect
Requirement 5: Any link on the page that refers back to a panel should trigger the effect
Since we’re all lazy developers, we don’t want to have to specifically markup arbitrary links on the page that should trigger this effect.
This is where Ariel’s localScroll plugin comes to the rescue. By just applying that plugin with our default settings any link on the page will trigger the effect. In addition, because we’re going to trigger our ’scrollerComplete’ function when the effect has finished, it will correctly select the navigation associated.
The jQuery Code
Here’s the code with all the above requirements added in. The code is commented to explain what we’re doing.
I’ve also decided to include a bonus requirement - to support both horizontal or vertical scrolling.
// when the DOM is ready...
$(document).ready(function () {
var $panels = $('#slider .scrollContainer > div');
var $container = $('#slider .scrollContainer');
// if false, we'll float all the panels left and fix the width
// of the container
var horizontal = true;
// float the panels left if we're going horizontal
if (horizontal) {
$panels.css({
'float' : 'left',
'position' : 'relative' // IE fix to ensure overflow is hidden
});
// calculate a new width for the container (so it holds all panels)
$container.css('width', $panels[0].offsetWidth * $panels.length);
}
// collect the scroll object, at the same time apply the hidden overflow
// to remove the default scrollbars that will appear
var $scroll = $('#slider .scroll').css('overflow', 'hidden');
// apply our left + right buttons
$scroll
.before('<img class="scrollButtons left" src="images/scroll_left.png" />')
.after('<img class="scrollButtons right" src="images/scroll_right.png" />');
// handle nav selection
function selectNav() {
$(this)
.parents('ul:first')
.find('a')
.removeClass('selected')
.end()
.end()
.addClass('selected');
}
$('#slider .navigation').find('a').click(selectNav);
// go find the navigation link that has this target and select the nav
function trigger(data) {
var el = $('#slider .navigation').find('a[href$="' + data.id + '"]').get(0);
selectNav.call(el);
}
if (window.location.hash) {
trigger({ id : window.location.hash.substr(1) });
} else {
$('ul.navigation a:first').click();
}
// offset is used to move to *exactly* the right place, since I'm using
// padding on my example, I need to subtract the amount of padding to
// the offset. Try removing this to get a good idea of the effect
var offset = parseInt((horizontal ?
$container.css('paddingTop') :
$container.css('paddingLeft'))
|| 0) * -1;
var scrollOptions = {
target: $scroll, // the element that has the overflow
// can be a selector which will be relative to the target
items: $panels,
navigation: '.navigation a',
// selectors are NOT relative to document, i.e. make sure they're unique
prev: 'img.left',
next: 'img.right',
// allow the scroll effect to run both directions
axis: 'xy',
onAfter: trigger, // our final callback
offset: offset,
// duration of the sliding effect
duration: 500,
// easing - can be used with the easing plugin:
// http://gsgd.co.uk/sandbox/jquery/easing/
easing: 'swing'
};
// apply serialScroll to the slider - we chose this plugin because it
// supports// the indexed next and previous scroll along with hooking
// in to our navigation.
$('#slider').serialScroll(scrollOptions);
// now apply localScroll to hook any other arbitrary links to trigger
// the effect
$.localScroll(scrollOptions);
// finally, if the URL has a hash, move the slider in to position,
// setting the duration to 1 because I don't want it to scroll in the
// very first page load. We don't always need this, but it ensures
// the positioning is absolutely spot on when the pages loads.
scrollOptions.duration = 1;
$.localScroll.hash(scrollOptions);
});
Wrap UP
That’s everything you need for the perfect slider. Ariel has lots of other examples of how the scroll plugins can be used on his web site so do check them out.
I’ll also be posting a follow up to last month’s survey.
If anyone has any questions, suggestions or better examples, please do share by dropping a comment on the site.
You should follow me on Twitter here I tweet about jQuery amongst the usual tweet-splurges!
Related screencasts
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

Play QuickTime version
Play Flash version

Olivier G. On 3rd June 2008 at 14:06
Hi !
Is it possible to be allowed to translate (to french) your article on jquery DOT info ?
Thanks .
Olivier G. On 3rd June 2008 at 14:06
(Seems that your antispam tool prevented me from posting an URL)
Tom On 3rd June 2008 at 16:06
Really excellent…also a good way to do ‘manual’ slideshows if one leaves off the tabs…
Please update your blog to tell which jQuery etc. to reference from Google’s jQuery hosting, this way we don’t have to worry about keeping the scripts up to date etc.
Google now hosts jQuery for anyone to use…
Thank you, Tom
Ben On 3rd June 2008 at 16:06
Awesome.
Remy On 3rd June 2008 at 16:06
@Tom - I’m still deliberating over the Google hosting of libraries, and whether I should start using it, particularly as I do a lot of work offline, so it’s handy to have my own copy.
Maybe the next screencast :-)
Ozh On 3rd June 2008 at 19:06
Very nice effect & implementation. I have one complaint though: “semantically” (not huge fan of this word) this is not awesome. For screen readers, with javascript disabled, for search engines and when printing the page,
in the source page instead of : title1 title2 title3 div1 div2 div3
it would be really very neater to read: title1 div1 title2 div2 title3 div3
Besides this (fairly important) aspect, it’s really nice.
Joe On 3rd June 2008 at 20:06
One thing to consider is page weight. If you have a code slider as a portion of say your site’s home page, all the divs contained inside will add substantial weight to your page, especially if each div has an image or two. I would posit a way to load only the divs and then onclick or slide or whatever, load in the src value of the img tags for that div. This way we don’t have to use AJAX and can save you a ton on the page weight. Not sure how this would be implemented with the Coda slider, but the weight of the page should not be ignored when containing this many divs of content.
Remy On 3rd June 2008 at 20:06
@Ozh - I’m not sure I follow. The source is: title1 div1, title2, div2, etc.
The first set of titles are navigation - as marked up with the class. There should be a skip link, but otherwise I see this solution as semantically sound — always important :-)
I think you might be misinterpreting the UL as titles perhaps?
Jigs On 3rd June 2008 at 22:06
Remy, this is a great tutorial and I appreciated the walkthrough/screencast. Your site is a great jQuery resource - I use the jQuery API browser every day and am glad to see its updated to 1.2.6 now! Well done.
Jigs On 3rd June 2008 at 23:06
Ah one thing that came to mind when I was playing around with your implementation - if you use this for a form (breaking the tabs into step1, step2, etc.) tabbing through the inputs basically breaks the entire flow by scrolling to the next “panel” without triggering the transition effect. I was thinking that one way to take care of this would be to give the non-active panels “visibility:hidden” so that the total width would still be reported accurately, but hitting the “tab” key would not scroll to them. What do you think? I haven’t tried it yet…
For panels of inconsistent height (i.e. one is 200px and another is 500px), I added some code to the trigger function that animates the height based on the id of the clicked element. That worked pretty well, too.
Thanks for your post!
Robert On 4th June 2008 at 08:06
Hi! This site is nice. Congrats for what you are sharing with us!
Robert from France.
Agent G On 4th June 2008 at 12:06
Very cool… really like the fact that you broke down each part rather than just sharing code with no indication of why each piece is there. One thing that might make it ever cooler is if you had an option to automatically rotate between panes. Like on this website: http://www.pricechopper.com/
Remy On 4th June 2008 at 12:06
@Agent G - I’ll be releasing a jQuery plugin which will support all these options via Ariel’s plugins. Subscribe to the feed and you’ll see it automatically.
shmoll On 5th June 2008 at 09:06
is there any way to “animate” the tabs? e.g. instead of the simple background color change on mouse-hover - would it be possible to for example to slide the tab in from the bottom and slide out on mouse-out. i figure it could be done with a background image that could be animated up and down but all my tries to mod you script simply didn’t work out. any hint towrds the right direction very appreciated.
stuart On 5th June 2008 at 10:06
very nice. well done. When I enter the hash in the url (#files) for example, it does NOT select the proper tab (it does select the proper div though) - it still defaults to the first one. This is in FF2.
Remy On 5th June 2008 at 13:06
@stuart - not sure why it’s not working for you, unless you’ve got an example I can see (feel free to drop me a link I’ll take a look if I have time), it definitely works for me - and the screencast was all done in FF2 which you can see it working fine.
Michael B On 5th June 2008 at 14:06
So this is really nice from a ui standpoint, but man it loads slowly, even when I load it locally!
Box renders with scroll bar, I can click on links, and then eventually the scrollER renders.
David655 On 6th June 2008 at 17:06
As it this isn’t cool enough. If you get pane rotation working with Ariel’s plugins this will be awesome.
Not trying to be pushy, but any idea when this plugin will be ready?
Remy On 6th June 2008 at 17:06
@David655 - no problem :-) I’ll be published next week along with a couple of other minor notices (particularly feedback from last months survey).
David655 On 6th June 2008 at 20:06
Very cool. I’m looking forward to it. I’m not sure what issues were revealed in your survey, but I do notice when I mouse-click to the right of the panel, the panel gets selected.
Also, please check out my implementation here: http://www.looklisteninteract.com/bettercoda/
I’ve attempted to put the nav buttons on the left side. There’s some weird padding issues. Can anyone offer suggestions? help?
Nikolay Kolev On 7th June 2008 at 01:06
I’m sorry, but Coda’s seems and feels better - scrolling is much smoother and seem more natural. I’ve seen many, many copycats, but the original is simply the best.
bensch@amin On 7th June 2008 at 15:06
hi remy!
thank you very much for your blog - it’s just great! unfortunately the coda-slider ist not valid css as horizontal-x will not pass the validator. any idea how we could fix that? ;o)
greetings from switzerland,
bensch @ amin.
chris On 8th June 2008 at 02:06
This was a very valuable demo! How would I need to modify coda-slider.js in order to have multiple sliders on a single page?
Thanks again!
Jasper On 11th June 2008 at 14:06
I would also like to know how to implement more than one slider on a single page. Anybody?
Derek On 12th June 2008 at 03:06
A quick note; under the required plugins section. The link to the localScroll is incorrect.
Thank you for sharing your knowledge.