How to Create a Slideshow Plugin with jQuery

There are hundreds of slideshow plugins out there, from all time favorites like Lightbox to the most advanced like Galleria. You might think, why make another one?. Well, most of us are using plugins like these on our websites, so why not use our own?

If you use slideshow plugins frequently you may realize that there are basically two different types, the ones that slide and the ones with effects that don't slide. Choosing one or the other depends on the approach you take when coding it.

In this tutorial I'll discuss the creation process of Powerslide, a plugin I just released that follows the “effects” approach. You should have a basic knowledge of HTML, CSS and jQuery to follow this tutorial.

How to Create a Slideshow Plugin with jQuery


Project Setup

I suggest you download this project setup template. It'll be easier than having to create all the files and link them. It has all you need to get started with this tutorial. If you decide to use your own project template keep in mind that the relative paths might change.

This is how the project template looks:

Setup

960gs and modernizr are not necessary but they're really useful. We're not going to use a separate IE stylesheet but you might need it if you want to mess with IE later.

The Structure

We're going to start with the bare bones of the slider.

First, let's set the base HTML for the slideshow in index.html:

<div id="slider">
	<img src="img/img1.jpg" title="caption" />
	<img src="img/img2.jpg" title="caption" />
	<img src="img/img3.jpg" title="caption" />
</div>

Let's also call the jQuery plugin on document load in js/scripts.js:

$(function(){
	$('#slider').powerSlide();
});

Now, we have everything we need to start coding the plugin.

Let's take a look at the structure we want to achieve:

Structure

The following code serves only as preview. This is what we will be generating with jQuery.

<div class="powerSlide">
	<div class="wrapper">
		<a href="" class="prev"></a>
		<a href="" class="next"></a>
		<div class="image">
			<img src="" alt="" />
			<p></p>
		</div>
	</div>
	<div class="nav"></div>
</div>

We need to generate all these elements in the DOM from the original HTML. So, let's begin with a basic jQuery plugin template.

Open js/powerSlide.js.

(function($){
	$.fn.powerSlide = function(options {  

		var opt = {
			// Options
		};

		return this.each(function() {        
			if (options) { 
				$.extend(opt, options);
			}
			/* Code goes here */			
		});
	};
})(jQuery);

Options are the variables that we can specify when calling the plugin on an element. These are the options we want to be able to change with their default values.

'width': 908, // Width and height of the images
'height': 340,
'position': 'bottom', // Position of the navigation
'bullets': false, // Show numbering navigation
'thumbs':  true, // Show thumbnail navigation
'row': 10, // Thumbnails per row
'auto': true, // Auto rotate
'autoSpeed': 4000,
'fadeSpeed': 1000

Now we can use the options with opt.option. For example, to use the width value we would write opt.width.

Inside the “each loop” and with the structure already in mind, we need to create the elements and assign them to variables so it's easier to refer to them later. Using firebug or the webkit developer tools we can track the elements as they're being created in the DOM.

/* Container and wrapper 
-----------------------------------------------*/ 
$(this).children().wrapAll('<div class="powerSlide" />'); 
var container = $(this).find('.powerSlide'); 
container.find('img').wrapAll('<div class="wrapper" />'); 
var wrapper = container.find('.wrapper'); 

/* Previous & next buttons 
-----------------------------------------------*/ 
wrapper.append('<a href="#" class="prev">Prev</a><a href="#" class="next">Next</a>'); 

/* Navigation & captions 
-----------------------------------------------*/ 
switch (opt.position) { // Navigation position 
		case 'top': container.prepend('<div class="nav" />'); break; 
		case 'bottom': container.append('<div class="nav" />'); break; 
} 

var nav = container.find('.nav'); 

wrapper.find('img').each(function(i){ 

	i += 1; // Start numbers at 1 

	if (opt.bullets === true) { // Bullet navigation 
			nav.append('<a href="#">'+ i +'</a>'); 
	} 

	if (opt.thumbs === true) { // Thumbnail navigation 
			nav.addClass('thumbs').append( 
				'<img class="thumb" src="'+ 
				$(this).attr('src') +'" alt=""/>'); 
	} 

	// Captions 
	var title = $(this).attr('title'); 
	$(this).wrapAll('<div class="image" />'); 
	if (title !== undefined) { 
			$(this).attr('title', ''); 
			$(this).after('<p>'+ title +'</p>'); 
	} 
});			 

You can now try changing the plugin options values to test different situations. Once the elements are created we can dive into the css.

This is just for the layout, but we will create a separate theme file for all the color customization and CSS3 goodness. This will help to keep the code clean and it'll be easier to create new themes later on.

Let's open css/powerSlide.css.

/* Wrapper
-------------------------------------------*/
.powerSlide .wrapper {
	overflow: hidden;
	padding: 15px;
	position: relative;
}

/* Image
-------------------------------------------*/
.powerSlide .wrapper img {
	position: absolute; /* They key to the “effects” approach */
}

/* Prev & Next buttons
-------------------------------------------*/
.powerSlide a.prev,
.powerSlide a.next {
	display: none;
	margin-top: -1em; /* Same as padding-top */
	padding: 1em 2em;
	position: absolute;
	text-decoration: none;
	top: 50%;
}
.powerSlide a.next {
	right: 0;
}

/* Caption
-------------------------------------------*/
.powerSlide .wrapper p {
	bottom: 0;
	display: none;
	padding: 1.5em;
	position: absolute;
}

/* Navigation
-------------------------------------------*/
.powerSlide .nav {
	margin: .5em 0;
	overflow: hidden;
}
.powerSlide .nav.thumbs {
	padding: 15px; /* Same as wrapper padding */
}
.powerSlide .nav img.thumb {
	cursor: pointer;
	float: left;
	margin: 2px;
	position: relative;
}
.powerSlide .nav img.thumb.current { /* Current thumbnail */
	z-index: 999;
}
.powerSlide .nav a {
	float:left;
	margin: .2em;
	min-width: 1em;
	padding: .2em .7em;
	text-align: center;
	text-decoration: none;
}
.powerSlide .nav a.current {} /* Current bullet */

As you can see the CSS is pretty straightforward. Elements that are inside the container have to be absolute positioned. Also be careful with margins and padding. We will generate the dimensions later on with jQuery.

The Slider Object

To store all the information and actions of the slider we're going to use an object. The most important concept of a slider is the index. We need to know the index of any given image at any time. The index will allow us to have control over which image needs to be shown when we trigger an event.

Here's a list of all the variables and a short description of what each one does.

  • imgs: the selector for all images.
  • imgCount: the number of images in the selection.
  • navNext: the next button.
  • navPrev: the previous button.
  • bullets: selector for all bullets in numbered navigation.
  • thumbs: selector for all thumbnails in thumbnail navigation.
  • captions: selector for all captions.
  • getCurrentIndex(): get the index of the current image at any given time.
  • go(index): go to an image of any given index.
  • next(): go to the next image.
  • prev(): go to the previous image.
  • init(): set width and height of the slideshow and calculte dimensions of dynamic elements.
/* Slider Object 
-----------------------------------------------*/		 
var Slider = function(){ 

this.imgs = wrapper.find('div.image'); 
this.imgCount = (this.imgs.length) - 1; // Match index 
this.navPrev = wrapper.find('a.prev'); 
this.navNext = wrapper.find('a.next'); 
this.bullets = container.find('.nav a'); 
this.thumbs = container.find('.nav img.thumb'); 
this.captions = this.imgs.find('p'); 

this.getCurrentIndex = function(){ // Index 
	return this.imgs.filter('.current').index(); 
}; 

this.go = function(index){ // Rotate images 
	this.imgs 
		.removeClass('current') 
		.fadeOut(opt.fadeSpeed) 
		.eq(index) 
		.fadeIn(opt.fadeSpeed) 
		.addClass('current'); 
	this.bullets 
		.removeClass('current') 
		.eq(index) 
		.addClass('current'); 
	this.thumbs 
		.removeClass('current') 
		.eq(index) 
		.addClass('current'); 
}; 

this.next = function(){ 
	var index = this.getCurrentIndex(); 
	if (index < this.imgCount) { 
		this.go(index + 1); // Go next 
	} else { 
		this.go(0); // If last go first 
	} 
}; 

this.prev = function(){ 
	var index = this.getCurrentIndex(); 
	if (index > 0) { 
		this.go(index - 1); // Go previous 
	} else { 
		this.go(this.imgCount); // If first go last 
	} 
};	 

this.init = function(){ // Init
	wrapper
		.width(opt.width)
		.height(opt.height); /* Set width and height */
	
	this.imgs.hide().first().addClass('current').show(); /* Set current image */
	this.bullets.first().addClass('current');
	this.thumbs.first().addClass('current');
	
	// Dimensions for thumbnails and captions
	var padding = wrapper.css('padding-left').replace('px', '');
	var captionsPadding = this.captions.css('padding-left').replace('px', '');
	nav.width(opt.width);
	if (opt.thumbs === true) { // thumbs
		var thumbBorder = this.thumbs.css('border-left-width').replace('px', '');
		var thumbMargin = this.thumbs.css('margin-right').replace('px', '');
		var thumbMaxWidth = opt.width/opt.row;
		this.thumbs.width( (thumbMaxWidth - (thumbMargin * 2)) - (thumbBorder * 2) );
	}
	this.captions // captions
		.width(opt.width - (captionsPadding * 2) + 'px')
		.css('margin-bottom', padding + 'px');
	this.navNext.css('margin-right', padding + 'px');
    }; 

};

Now that the object is defined we have to create a new instance and load it with the init() function.

var slider = new Slider();
slider.init();

Events

Mouse Events

We want to trigger the following events:

  • Click next and go to next image
  • Click prev and go to previous image
  • Click on a numbered bullet and go to that image
  • Click on thumbnail and go to that image
  • Hover image and show caption and buttons

We have to disable the default behavior of the link items with e.preventDefault() to avoid jumping to the top of the page when clicking on empty links.

Also make sure you hide the captions when not hovering over the image. I think the code for this part could be DRYer but it's good for readability.

wrapper.hover(function(){ // Hover image wrapper
	slider.captions.stop(true, true).fadeToggle();
	slider.navNext.stop(true, true).fadeToggle();
	slider.navPrev.stop(true, true).fadeToggle();
});
slider.navNext.click(function(e){ // Click next button
	e.preventDefault();
	slider.next(); 
});
slider.navPrev.click(function(e){ // Click previous button
	e.preventDefault();
	slider.prev();
});
slider.bullets.click(function(e){  // Click numbered bullet
	e.preventDefault(); slider.captions.hide();
	slider.go($(this).index());
});
slider.thumbs.click(function(){ // Click thumbnail
	slider.captions.hide();
	slider.go($(this).index());
});

Auto Rotate Images

This part is kind of boring, just make sure the caption is hidden when not hovering over the image.

if (opt.auto === true) {
	var timer = function(){
			slider.next();
			slider.captions.hide();
	};
	var interval = setInterval(timer, opt.autoSpeed);
	
	// Pause when hovering image
	wrapper.hover(function(){clearInterval(interval);}, function(){interval=setInterval(timer, opt.autoSpeed);});

	// Reset timer when clicking thumbnail or bullet
	slider.thumbs.click(function(){clearInterval(interval); interval=setInterval(timer, opt.autoSpeed);});
	slider.bullets.click(function(){clearInterval(interval); interval=setInterval(timer, opt.autoSpeed);});	
}

Creating a Theme

Everything should be working at this point. Let's make it pretty then.

Create the file css/powerSlide_theme.css and include it in index.html.

<link href="css/powerSlide_theme.css" rel="stylesheet" type="text/css" media="screen"/>

And with the help of Colorzilla CSS3 gradient generator let's add some styles.

/* Wrapper
-------------------------------------------*/
.powerSlide .wrapper {
	background:#fff;
	border:1px solid #999;
}

/* Prev & Next buttons
-------------------------------------------*/
.powerSlide a.prev,
.powerSlide a.next {
	background:#fff;
	box-shadow:2px 0 2px rgba(0,0,0,.3);
	color:#000;
	font:bold 10px Arial;
}
.powerSlide a.next {
	box-shadow:-2px 0 2px rgba(0,0,0,.3);
}

/* Caption
-------------------------------------------*/
.powerSlide .wrapper p {
	background:#000;
	background:rgba(0,0,0,.7);
	color:#fff;
}

/* Navigation
-------------------------------------------*/
.powerSlide .nav.thumbs {
	background:#b5bdc8;
	border:1px solid #999;
}
.powerSlide .nav img.thumb {
margin: 3px;
	box-shadow:0 0 2px #666;
	border: 4px solid transparent;
	filter:alpha(opacity=40);
	opacity:.4;
}
.powerSlide .nav a {
	background:#7d7e7d;
	background:-moz-linear-gradient(top, #7d7e7d 0%, #0e0e0e 100%);
	background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#7d7e7d), color-stop(100%,#0e0e0e));
	background:-webkit-linear-gradient(top,#7d7e7d0%,#0e0e0e100%);
	background:-o-linear-gradient(top,#7d7e7d0%,#0e0e0e100%);
	background:-ms-linear-gradient(top,#7d7e7d0%,#0e0e0e100%);
	background:linear-gradient(top,#7d7e7d0%,#0e0e0e100%);
	border-radius:3px;
	color:#fff;
	filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7d7e7d',endColorstr='#0e0e0e',GradientType=0);
	font:bold 12px Arial;
}
.powerSlide .nav img.thumb.current {
	box-shadow:0 0 10px #fff;
	border: 4px solid #fff;
	filter:alpha(opacity=100);
	opacity:1;
}
.powerSlide .nav a.current {
	background:#1e5799;
	background:-moz-linear-gradient(top, #1e5799 0%, #2989d8 50%, #207cca 51%, #7db9e8 100%);
	background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(50%,#2989d8), color-stop(51%,#207cca), color-stop(100%,#7db9e8));
	background:-webkit-linear-gradient(top,#1e57990%,#2989d850%,#207cca51%,#7db9e8100%);
	background:-o-linear-gradient(top,#1e57990%,#2989d850%,#207cca51%,#7db9e8100%);
	background:-ms-linear-gradient(top,#1e57990%,#2989d850%,#207cca51%,#7db9e8100%);
	background:linear-gradient(top,#1e57990%,#2989d850%,#207cca51%,#7db9e8100%);
	filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#1E5799',endColorstr='#7db9e8',GradientType=0);
}

Notes

The plugin works in IE7+ and all other modern browsers. For some reason, even in IE9, the images don't fade. This problem must be related to the fadeIn and fadeOut jQuery functions.

Bullets and thumbnails can't be displayed at the same time but who wants to have them both anyway?

I will be glad to answer any questions. I encourage you to create your own theme and post it here. It'll be great to have a community theme repository.

Opinions expressed in this article are those of the author and not necessarily those of Onextrapixel.