A better jQuery slider

If you haven’t seen Apple’s prod­uct slider in action, you must check it out. It’s a hor­i­zon­tal gallery of their prod­ucts with hor­i­zon­tal slider con­trolled with javascript. I believe they are using the scrip­tac­u­lous motion for this, but there should be a way to build this in jQuery.

Truth be told, I was a die-​hard fanatic for scriptaculous/​prototype, but after ExpressionEngine announce their adop­tion of jQuery for EE 2.0, it was time to grow beyond the one frame­work pony.

Luckily, some­one already set out to build the slider in jQuery. And major hat’s off to Remy Sharp for a great approach to this. If you look­ing for a way to build your own slider, start there first.

The Problem

Actually, there was no prob­lem until I had a client to didn’t fit into the strict mold set out for this wid­get. As with most inven­tive solu­tions, they are begat from necessity.

The slider is built on an unordered list, but doesn’t sup­port nested lists. For my needs, the slider had to be able to not only show mul­ti­ple cat­e­gories of prod­ucts, but also visu­ally differentiate.

Now, this can be resolved by man­u­ally set­ting css val­ues. This approach would “hard code” the posi­tions of the cat­e­gories. That isn’t very code may be puled from a database.

Also, the slider needs to work with­out javascript. I know. Who doesn’t use javascript? The answer is, I don’t know, but some­one always points out if some­thing doesn’t degrade properly.

The Solution

I built this out back­wards, but it works. So don’t ques­tion it. This demo shows the slider with wire­frames to show off the block level div’s and li’s.

You can see by this code exam­ple that the lists are sim­ple and even has a space for the cat­e­gory descrip­tion if your site requires it.

HTML

This is a sim­ple tem­plate shoing how to for­mat your prod­uct groups and your cat­e­gories for them. There can be as many needed. This exam­ple should be able to dis­play them all successfully.

<div id="mlSlideWidget" class="slideWidget">
	<div id="sliderWindow" class="sliderGallery">
		<ul class="sliderList">
			<!-- Each category -->
			<li class="group">
				<div class="groupContent">
					<!-- Description info for category -->
					<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
					<ul class="groupButtons">
						<!-- Each product for this category -->
						<li class="singleButton"><a href="#">Product 1</a></li>
						<li class="singleButton"><a href="#">Product 2</a></li>
						<li class="singleButton"><a href="#">Product 3</a></li>
					</ul>
				</div>
			</li>
			<li class="group">
				<div class="groupContent">
					<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
					<ul class="groupButtons">
						<li class="singleButton"><a href="#">Product 4</a></li>
						<li class="singleButton"><a href="#">Product 5</a></li>
						<li class="singleButton"><a href="#">Product 6</a></li>
					</ul>
				</div>
			</li>
			<li class="group">
				<div class="groupContent">
					<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
					<ul class="groupButtons">
						<li class="singleButton"><a href="#">Product 7</a></li>
						<li class="singleButton"><a href="#">Product 8</a></li>
						<li class="singleButton"><a href="#">Product 9</a></li>
					</ul>
				</div>
			</li>
			<li class="group">
				<div class="groupContent">
					<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
					<ul class="groupButtons">
						<li class="singleButton"><a href="#">Product 10</a></li>
						<li class="singleButton"><a href="#">Product 11</a></li>
						<li class="singleButton"><a href="#">Product 12</a></li>
					</ul>
				</div>
			</li>
		</ul>
	</div>

	<div id="scroll" class="slider">
		<!-- the handler to action the slide -->
		<div class="handle"></div>
		<!-- labels appear against the slider, as pointers to the user -->
		<ul>
			<!-- These are categories -->
			<li class="marker">Category 1</li>
			<li class="marker">Category 2</li>
			<li class="marker">Category 3</li>
			<li class="marker">Category 4</li>
		</ul>
		<div class="fakeHandle">
			<table width="100%" cellpadding="0" cellspacing="0" border="0">
				<tr>
					<td width="15"><img src="images/handle-left.png" width="15" height="115" alt="<" /></td>
					<td background="images/handle-middle.png"> </td>
					<td width="15"><img src="images/handle-right.png" width="15" height="115" alt=">" /></td>
				</tr>
			</table>
		</div>
	</div>

</div>

CSS

I threw in a dash of YUI’s css reset. Most of the id and class names are generic. I would rec­om­mend renam­ing these to some­thing more spe­cific to avoid conflict.

body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, img {border-width: 0px;margin:0;padding:0;}
#mlSlideWidget {
	font:11px arial,helvetica,clean,sans-serif;
	*font-size:small;
	*font:x-small;
	position: relative;
	height: 361px;
	width: 659px;
	background: url(images/slider-background.jpg) left top no-repeat;
}

#sliderWindow {
	margin: 1px;
	height: 365px;
	position: relative;
	width: 655px;
}

.sliderGallery {
	overflow: auto;
}

.sliderGallery ul.sliderList {
	top: 0px;
	left: 0px;
	margin-top: 135px;
	padding-bottom: 0;
	position: absolute;
	list-style: none;
	height: 210px;
	overflow: auto;
	width: 2000px;
	white-space: nowrap;
}

#mlSlideWidget .sliderGallery ul li.group {
	margin-left: 7px;
	margin-right: 7px;
	display: inline;
	float: left;
}

#mlSlideWidget .sliderGallery ul li.group .groupContent {
	margin-right: 10px;
	padding-left: 10px;
	padding-top: 10px;
	padding-bottom: 10px;
}

ul.groupButtons {
	height: 120px;
}

li.group {
	background: url(images/group-backgroundcap.png) no-repeat right top;
}

li.group .groupContent{
	background: url(images/group-background.png) no-repeat left top;
}

li.group p {
	width: 430px;
	height: 70px;
	font-size: 10px;
	line-height: 12px;
	white-space: normal;
}

li.group ul.groupButtons {
	list-style: none;
	white-space: nowrap;
}

li.group ul.groupButtons li.singleButton {
	width: 140px;
	float: left;
	height: 118px;
	text-align: center;
}
#scroll {
	position: absolute;
	top: 0px;
	left: 0px;
	z-index: 100;
	width: 639px;
	margin-left: 10px;
	margin-right: 8px;
}

#scroll ul {
	height: 123px;
	list-style: none;
}

#scroll li.marker {
	margin-top: 4px;
	padding-top: 60px;
	text-align: center;
	z-index: 90;
	font-size: 10px;
	height: 30px;
	width: 55px;
	padding-left: 20px;
	padding-right: 20px;
	margin-left: 10px;
	margin-right: 10px;
	float: left;
}

.handle {
	position: absolute;
	top: 8px;
	left: 0;
	z-index: 110;
	height: 115px;
}

.fakeHandle {
	position: absolute;
	top: 8px;
	left: 0;
	z-index: 80;
	height: 115px;
}

jQuery UI will need to be built with UI Core, Draggable, Slider, Effects Core, and Slide. Some of the val­ues of this rules in the exam­ple are built to my specs and can be mod­i­fied to what­ever specs fit your site. Note the ver­sions of jQuery at the time of this article.

This exam­ple doesn’t know how many objects exist in the slider. If you use a set amount, this exam­ple can be greatly reduced. However, if your cat­e­gories are man­aged via a data­base or cms tool, the num­ber of objects used can change.

Javascript

Try not to laugh at my n00b code. I’m sure there are much bet­ter ways to write this.


//basic rules for controling the divs
$(document).ready(function(){

   //fix css rules
   //above css written for pages loaded without javascript
   $('.sliderGallery').css({'overflow-x': 'hidden', 'overflow-y': 'visible'});
   $('li.marker').css({'float': 'none', 'position': 'absolute', 'top': 0, 'left': 0});

	//set up some public variables and arrays
	var scrollBar = 639;
	var sliderListOffsetLeft = $('.sliderList').offset();
	var productWidth = 0;
	var groupWidth = 0;
	var sliderWidth = new Array();
	var sliderLeft = new Array();
	var groupRangeLeft = new Array();
	var marker = new Array();

	//measure the width of all the sub-categories
	//and the sub-sub-categories while we're at it
	$('li.group').each(function (i) {
		sliderLeft[i] = productWidth;
		$('li.singleButton', this).each(function (i) {
			groupWidth = groupWidth + $(this).width()
		});
		//hard coded minimum width for groups
		if (groupWidth < 450)
			groupWidth = 450;
		else
			groupWidth += 20;
		productWidth = productWidth + groupWidth + 16;
		//set width for each sub-caterogy
		sliderWidth[i] = groupWidth + 14;
		$(this).css({width: groupWidth});
		//reset the variable
		groupWidth = 0;
		//used in another function not used in demo
		groupOffsetleft = $(this).offset();
		groupRangeLeft[i] = groupOffsetleft.left - sliderListOffsetLeft.left - ((i > 0) ? 14 : 7 );
	});

	//Fix css to fit with slider content
	$('ul.sliderList').css({width: productWidth + 14});

	//Variables to make math easier
	var handle = (scrollBar / productWidth) * scrollBar;
	var markerLeft = 0;
	var scrollBarVisible = scrollBar - handle;

	$('li.marker').each(function (i) {
		markerLeft = (sliderLeft[i] / productWidth * scrollBar);
		markerWidth = (sliderWidth[i] / productWidth * scrollBar);
		$(this).css({left: markerLeft});
		marker[i] = (markerLeft / scrollBarVisible) * 100;
	});

	$('div.handle').css({width: handle});
	$('div.fakeHandle').css({width: handle});

	if (productWidth > scrollBar)
	{
		$('div.slideWidget').each(function () {
			var ul = $('ul.sliderList', this);
			var fake = $('.fakeHandle', this);

			var slider = $('.slider', this).slider({
				handle: '.handle',
				minValue: 0,
				maxValue: productWidth,
				slide: function (ev, ui) {
					ul.css('left', '-' + Math.round(ui.value / 100 * (productWidth - 657)) + 'px');
					fake.css('left', Math.round(ui.value / 100 * (scrollBar - handle)) + 'px');
				},
				stop: function (ev, ui) {
					move = ui.value / 100 * (productWidth - 657);
					ul.animate({ 'left' : '-' + Math.round(move) + 'px' }, 500);
					fake.animate({'left' : Math.round(ui.value / 100 * (scrollBar - handle)) + 'px'}, 500);
				}
			});
		})
	}

	else {
		$('.fakeHandle').css({width: 0});
		$('.handle').css({width: 0});
		$('.fakeHandle').css('visibility', 'hidden');
		$('.handle').css('visibility', 'hidden');
	}

});

Notice the ver­sions of jQuery and UI. 1.7.1 has issues with this solu­tion. As of yet, I haven’t found a fix.

This entry was posted in Code, Design and tagged , , , . Bookmark the permalink.

One Response to A better jQuery slider

  1. Harley says:

    that is pretty badass.