Creating a JavaScript slideshow: the JavaScript code

Creating a JavaScript slideshow: the JavaScript code

It's time to complete our tutorial and take a closer look at the JavaScript component of a slideshow.

In our previous articles we covered the HTML and CSS components of a JavaScript slideshow. Now it's time to complete our tutorial and take a closer look at the JavaScript component.

Slideshows without pagination

Slideshows without a pagination system simply rely on two actions (or controls), namely the "Next" and "Previous" buttons. These buttons can be seen as increment and decrement operators, respectively.

There's always a pointer (or cursor) that will be incremented or decremented every time a user clicks on these buttons. The pointer is initially set to 0 and its purpose is to select the current slide exactly as we do with arrays.

So when we click for the first time on the "Next" button, the pointer will be incremented by 1 and we can get the second slide (remember that we're dealing with an array-like structure). Then we click on the "Previous" button and the pointer is decremented by 1 so we get the first slide. And so on.

The following schema shows how this is implemented (we use jQuery here but the same technique can be applied to plain JavaScript slideshows as well).

Slideshow's counter scheme

We're using the jQuery's .eq() method together with our pointer to select the current slide. In plain JavaScript this will become:


function Slideshow( element ) {
	this.el = document.querySelector( element );
	this.init();	
}

Slideshow.prototype = {
	init: function() {
		this.slides = this.el.querySelectorAll( ".slide" );
		//...
	},
	_slideTo: function( pointer ) {
		var currentSlide = this.slides[pointer];
		//...
	}
};

Remember that a NodeList uses indices just like an actual array. Another way to select the current slide in plain JavaScript is to make use of CSS3 selectors:


Slideshow.prototype = {
	init: function() {
		//...
	},
	_slideTo: function( pointer ) {
	
        var n = pointer + 1;	
		var currentSlide = this.el.querySelector( ".slide:nth-child(" + n + ")" );
		//...
	}
};

The CSS3 :nth-child() selector counts elements starting from 1, not from 0 so we need to increment by 1 our pointer before using it to select the current slide.

Once selected the current slide we need to make its parent container move from right to left. In jQuery we can use the .animate() method:


(function( $ ) {
	$.fn.slideshow = function( options ) {
		options = $.extend({
			wrapper: ".slider-wrapper",
			slides: ".slide",
			//...
			speed: 500,
			easing: "linear"
		}, options);
		
		var slideTo = function( slide, element ) {
			var $currentSlide = $( options.slides, element ).eq( slide );
			
			$( options.wrapper, element ).
			animate({
				left: - $currentSlide.position().left
			}, options.speed, options.easing );	
			
		};

        //...
	};

})( jQuery );

We use the current left offset of the selected slide to create our sliding effect. In plain JavaScript there's no .animate() method, so the best thing we can do is to take advantage of CSS transitions:


.slider-wrapper {
	position: relative; // Required
	transition: left 500ms linear;
}

Then we can simply change the left property dynamically:


function Slideshow( element ) {
	this.el = document.querySelector( element );
	this.init();	
}

Slideshow.prototype = {
	init: function() {
	    this.wrapper = this.el.querySelector( ".slider-wrapper" );
		this.slides = this.el.querySelectorAll( ".slide" );
		//...
	},
	_slideTo: function( pointer ) {
		var currentSlide = this.slides[pointer];
		this.wrapper.style.left = "-" + currentSlide.offsetLeft + "px";
	}
};

Now it's time to bind a click event to each control. In jQuery we can use the .on() method whereas in plain JavaScript we'll attach our events through the addEventListener() method.

We also need to check whether our pointer has reached a specific limit, namely 0 for the "Previous" button and the total number of slides for the "Next" button. In both cases the "Previous" or the "Next" button must be shown or hidden:


(function( $ ) {
	$.fn.slideshow = function( options ) {
		options = $.extend({
			wrapper: ".slider-wrapper",
			slides: ".slide",
			previous: ".slider-previous",
			next: ".slider-next",
			//...
			speed: 500,
			easing: "linear"
		}, options);
		
		var slideTo = function( slide, element ) {
			var $currentSlide = $( options.slides, element ).eq( slide );
			
			$( options.wrapper, element ).
			animate({
				left: - $currentSlide.position().left
			}, options.speed, options.easing );	
			
		};

        return this.each(function() {
			var $element = $( this ),
				$previous = $( options.previous, $element ),
				$next = $( options.next, $element ),
				index = 0,
				total = $( options.slides ).length;
				
			$next.on( "click", function() {
				index++;
				$previous.show();
				
				if( index == total - 1 ) {
					index = total - 1;
					$next.hide();	
				}
				
				slideTo( index, $element );	
				
			});
			
			$previous.on( "click", function() {
				index--;
				$next.show();
				
				if( index == 0 ) {
					index = 0;
					$previous.hide();	
				}
				
				slideTo( index, $element );	
				
			});

				
		});
	};

})( jQuery );

In plain JavaScript this becomes:


function Slideshow( element ) {
	this.el = document.querySelector( element );
	this.init();	
}

Slideshow.prototype = {
	init: function() {
	    this.wrapper = this.el.querySelector( ".slider-wrapper" );
		this.slides = this.el.querySelectorAll( ".slide" );
		this.previous = this.el.querySelector( ".slider-previous" );
		this.next = this.el.querySelector( ".slider-next" );
		this.index = 0;
		this.total = this.slides.length;
			
		this.actions();	
	},
	_slideTo: function( pointer ) {
		var currentSlide = this.slides[pointer];
		this.wrapper.style.left = "-" + currentSlide.offsetLeft + "px";
	},
	actions: function() {
		var self = this;
		self.next.addEventListener( "click", function() {
			self.index++;
			self.previous.style.display = "block";
				
			if( self.index == self.total - 1 ) {
				self.index = self.total - 1;
				self.next.style.display = "none";
			}
				
			self._slideTo( self.index );
				
		}, false);
			
		self.previous.addEventListener( "click", function() {
			self.index--;
			self.next.style.display = "block";
				
			if( self.index == 0 ) {
				self.index = 0;
				self.previous.style.display = "none";
			}
				
			self._slideTo( self.index );
				
		}, false);
	}

};

Examples

Slideshows with pagination

In slideshows with pagination each pagination link corresponds to a single slide, so there's no more need for a pointer. Each link may have an hash or an attribute's value that points to a specific slide.

Animations don't change. What is different here is the way by which a user navigates the slides. In jQuery we may have the following code:


(function( $ ) {
	$.fn.slideshow = function( options ) {
		options = $.extend({
			wrapper: ".slider-wrapper",
			slides: ".slide",
			nav: ".slider-nav",
			speed: 500,
			easing: "linear"
		}, options);
		
		var slideTo = function( slide, element ) {
			var $currentSlide = $( options.slides, element ).eq( slide );
			
			$( options.wrapper, element ).
			animate({
				left: - $currentSlide.position().left
			}, options.speed, options.easing );	
			
		};

        return this.each(function() {
			var $element = $( this ),
				$navigationLinks = $( "a", options.nav );
				
				$navigationLinks.on( "click", function( e ) {
					e.preventDefault();
					var $a = $( this ),
						$slide = $( $a.attr( "href" ) );
						
						slideTo( $slide, $element );
						$a.addClass( "current" ).siblings().
						removeClass( "current" );
					
				});
				
				
		});
	};

})( jQuery );

In this case each link's anchor corresponds to the ID of a specific slide. We can use this anchor also in plain JavaScript or a custom data attribute that stores the numeric index of each slide within the NodeList:


function Slider( element ) {
	this.el = document.querySelector( element );
	this.init();
}
Slider.prototype = {
	init: function() {
		this.links = this.el.querySelectorAll( "#slider-nav a" );
		this.wrapper = this.el.querySelector( "#slider-wrapper" );
		this.navigate();
	},
	navigate: function() {
		for ( var i = 0; i < this.links.length; ++i ) {
			var link = this.links[i];
			this.slide( link );
		}
	},
	slide: function( element ) {
		var self = this;
		element.addEventListener( "click", function( e ) {
			e.preventDefault();
			var a = this;
			self.setCurrentLink( a );
			var index = parseInt( a.getAttribute( "data-slide" ), 10 ) + 1;
			var currentSlide = self.el.querySelector( ".slide:nth-child(" + index + ")" );
			self.wrapper.style.left = "-" + currentSlide.offsetLeft + "px";
		},
		false);
	},
	setCurrentLink: function(link) {
		var parent = link.parentNode;
		var a = parent.querySelectorAll( "a" );
		link.className = "current";
		for ( var j = 0; j < a.length; ++j ) {
			var cur = a[j];
			if ( cur !== link ) {
				cur.className = "";
			}
		}
	}
};

Examples

Complete examples

GitHub

Conclusion

There's much more to say about JavaScript slideshows but we've covered a lot in these three tutorials so that you can start implementing your solutions by yourself.