Steven Berridge

Web Developer

Creating a Simple JavaScript Slider

The aim of this tutorial will be to create a slider like the one being used on this page for all the project thumbnails.

This tutorial will touch on some simple CSS3 transition techniques as well as using JavaScript objects within your projects.

The HTML Layout

Below is all the HTML we'll be using to create our slider.

<div width='300px' height='230px'>
	<div>
		<h1>Slide 1</h1>
	</div>
	<div>
		<h1>Slide 2</h1>
	</div>
	<div>
		<h1>Slide 3</h1>
	</div>
</div>

This will make a slider with three slides, each containing a <h1> tag.

Example

Slide 1

Slide 2

Slide 3

The CSS
/**
	* Styles for positioning the slides
	* and controlling slide visibility
	*/
.slider {
	position: relative;
}
.slider-inner {
	width: inherit;
	height: inherit;
	position: relative;
	overflow: hidden;
}
.slider .item {
	position: absolute;
	height: inherit;
	width: inherit;
	top: 0;
	display: none;
}
.slider .active {
	display: block;
	
	/**
	 * Default positions for the active slide
	 */
	left: 0;
	top: 0;
	opacity: 1;

	/**
	 * Transition styles to control animation between the slides
	 */
	transition: left ease, top ease, opacity ease;
	-webkit-transition: left ease, top ease, opacity ease;
	-moz-transition: left ease, top ease, opacity ease;
}

/**
 * Styles for positioning slides as they enter the slider and leave it
 */
.slider .item.left {
	left: -100%;
}
.slider .item.right {
	left: 100%;
}
.slider .item.fade {
	opacity: 0;
}
.slider .top {
	top: -100%;
}
.slider .bottom {
	top: 100%;
}
/**
 * end of positioning styles
 */

.slider img {
	height: inherit;
	width: inherit;
}

/**
 * Styles for slider controls
 * next & previous buttons and slide buttons
 */
.slider > ul {
	list-style: none;
	margin: 0;
	padding: 0;
	position: absolute;
	top: 10px;
	left: 50%;
}
.slider > ul li {
	float: left;
	margin-right: 10px;
	
}
.slider > ul li:last-of-type {
	margin-right: 0;
}
.slider a {
	color: #fff;
	text-decoration: none;
}
.slider .control, .slider > ul {
	opacity: 0;
	transition: opacity 0.2s;
	-webkit-transition: opacity 0.2s;
	-moz-transition: opacity 0.2s;
}
.slider:hover .control, .slider:hover > ul {
	opacity: 1;
}
.slider .control {
	position: absolute;
	width: 20px;
	text-align: center;
	vertical-align: middle;
	background: #333;
	display: block;
	border-radius: 5px;
	border: 1px solid rgba(255,255,255,0.5);
}
.slider .control.left {
	left: 10px;
}
.slider .control.right {
	right: 10px;
}
.slider > ul li a {
	display: block;
	width: 10px;
	height: 10px;
	border-radius: 5px;
	background: #333;
	text-indent: -9999px;
	border: 1px solid rgba(255,255,255,0.5);
}
.slider > ul li.current a {
	background: #ccc;
}
The JavaScript Object

The slider will be controlled by a single JavaScript object that contains all the variables and functions needed to move the slider from one element to the next, below is a bare object showing the object and functions that we'll need.


/**
 * Slider object containing variables and functions needed to create a slider
 * Param: elem, parent element containing each slide
 * Param: delay, time in milliseconds between each slide movement
 * Param: speed, time in milliseconds it takes to move from one slide to the next
 * Param: controls, boolean toggling slider controls
 * Param: type, type of sliding animation to use
 */
function Slider(elem,delay,speed,controls,type) {

	/**
	 * Timer object used for controlling timing between slide movements.
	 * Param: callback, function to call on each iteration of the timing loop
	 * Param: delay, time in milliseconds between each loop
	 */
	function Timer(callback, delay) {
	    
	}

	/**
	 * Move to the next slide.
	 * Param: direction, direction to move (optional)
	 * Param: index, index of slide to move to (optional)
	 */
	function next(direction,index) {

	}

	/**
	 * Event handler for control button presses
	 * Param: e, event information
	 */
	function buttonHndl(e) {
				
	}

	/**
	 * publically accessible function for pausing the slider
	 */
	this.pause = function() {
	
	}
	
	/**
	 * Publically accessible function for resuming the slider
	 */
	this.resume = function() {

	}
};

This object will control everything to do with the slider, the purpose for each of these functions is described within the comments of the code.

Extending the HTMLCollection object

Before we begin filling out the functionality for the slider we'll need to extend the native HTMLCollection object. By doing this we can add a function for finding the index of an element within the collection.

HTMLCollection.prototype.indexOf = function(element) {
	for(var i = 0, l = this.length; i < l; i++) {
		if(this[i] === element) {
			return i;
		}
	}
	return -1;
};

HTMLCollection objects are created whenever you run a function upon the Document Object Model that returns multiple elements, such as document.getElementsByTagName('a');. Now that the indexOf function has been added we can easily figure out what position an element appears at within a collection, which will come in handy later.

Registering The Slide Classes

A number of different CSS classes are used to control the positioning of the slides as they enter and leave the slider, we can register these as different 'types' of slider, these will be added to the Slider object in a way that will allow them to be accessed by all instances of the Slider object.

Slider.types = {
	horizontal: {
		/**
		 * classes for moving a slide 'out' of view
		 */
		out: {
			next: "left",
			previous: "right"
		},
		/**
		 * classes for moving a slide 'in' to view
		 */
		in: {
			next: "right",
			previous: "left"
		}
	},
	vertical: {
		out: {
			next: "bottom",
			previous: "top"
		},
		in: {
			next: "top",
			previous: "bottom"
		}
	},
	fade: {
		out: {
			next: "fade",
			previous: "fade"
		},
		in: {
			next: "fade",
			previous: "fade"
		}
	}
};

This will register 3 different 'sliding' styles, moving the slides into frame by sliding them horizontally, sliding them vertically and by fading them.

The keys define when these classes are applied to a slide, the 'out' set is used when a slide is moving out of frame, the 'in' set is used when a slide moves into frame. The 'next' and 'previous' classes are then used depending on which direction the slider is moving in, fowards or backwards.

For example, when using the 'horizontal' style we will position the slide that is moving into frame to the right side of the slider and move the slide that is moving out of frame to the left.

Constructing the Slider Object

When creating a new slider we must first create all the variables, make all the HTML elements and add the event listeners needed for the slider to work. This is done in the main body of the Slider object.

First we'll define all the variables we'll need.

//Defining a 'that' variable lets us access the object instances properties from within the functions
var that = this, 
moving = false,
buttons,
/**
 * The controls and type variables are passed into the object
 * when it is created, we can define 'default' values if they are
 * not defined.
 */
controls = typeof controls !== "undefined" ? controls : true, 
type = typeof type !== "undefined" ? type : "horizontal",
current = null,
inner = document.createElement('div');

Next we need to set up the elements that contain our slider, first of all we'll set up our main containing element by adding the slider class and setting the height and width of the slider. We'll also add the instance of the slider to the element so we can easily access it later.

if(!/ ?slider ?/.test(elem.className)) {
	elem.className += " slider";
}
if(elem.getAttribute("height") !== null) {
	elem.style.height = elem.getAttribute("height");
}
if(elem.getAttribute("width") !== null) {
	elem.style.width = elem.getAttribute("width");	
}

elem.slider = this;

This uses a quick regular expression check to see if the class exists, if it doesn't it is added. The height and width of the slider is set using the 'height' and 'width' attributes set on the element.

The next task involves setting up the 'inner' section of the slider, this will involve moving all the slides from our main containing element into the 'inner' element defined with our variables. We'll also set up our slides at the same time by adding styles to control the speed of the animations.

inner.className = "slider-inner";

/**
 * continue looping until the main sliding element has no children
 */
while(elem.children.length > 0) {
	/**
	 * add the 'item' class to the first child
	 */
	elem.children[0].className = "item";

	/**
	 * set the animation speed using the value passed into the slider
	 * we need to divide the speed by 1000 to convert from milliseconds to seconds
	 * add browser specific transitionDuration styles
	 */
	elem.children[0].style.transitionDuration = speed/1000+"s";
	elem.children[0].style.webkitTransitionDuration = speed/1000+"s";
	elem.children[0].style.mozTransitionDuration = speed/1000+"s";

	/**
	 * finally move the first child from the main slider to the inner element
	 */
	inner.appendChild(elem.children[0]);

	/**
	 * loop until there are no more children
	 */
}

/**
 * finally set the first child of the inner element to be the active slide
 * then add inner to the main slider element.
 */
inner.children[0].className = "item active";
elem.appendChild(inner);

Next we need to create all the elements for the slider controls, these will only be created if the slider control variable is set to true when constructing the slider.

Three types of control need to be created, a next button, a previous button, and buttons for moving to a specific slide.

/**
 * First check that we want to display controls
 */
if(controls) {
	/**
	 * Create a UL element to hold the slide buttons
	 */
	var ul = document.createElement('ul');
	/**
	 * loop over all the slides within the 'inner' element
	 */
	for(var i = 0, l = inner.children.length; i < l; i++) {
		/**
		 * for each slide create an LI element and an A element
		 * add an event listener listening for a 'click'
		 * this event uses the buttonHndl function created in the object overview section
		 * add the A element to the LI element then add the LI element to the UL element
		 */
		var li = document.createElement('li');
		var a = document.createElement('a');
		a.addEventListener('click',buttonHndl);
		a.innerHTML = i;
		li.appendChild(a);
		a.href = "#";
		ul.appendChild(li);
	}

	/**
	 * Add the UL element to the main slider element
	 */
	elem.appendChild(ul);

	/**
	 * Center the UL element within the main slider element
	 * by setting the left style to half the slider width - half the UL width
	 */
	ul.style.left = (elem.offsetWidth/2-ul.offsetWidth/2)+"px";
	buttons = ul;

	/**
	 * Add the 'current' class the first first button
	 * this button represents the first slide
	 */
	ul.children[0].className = "current";

	/**
	 * This creates the 'next' button for moving forwards through the slides
	 * 
	 */
	var nextBtn = document.createElement('a');
	nextBtn.href = "#";
	nextBtn.innerHTML = ">";
	nextBtn.className = "control right";

	/**
	 * Add an event listener to detect when the user clicks the button
	 */
	nextBtn.addEventListener('click',function(e) {
		e.preventDefault();

		/**
		 * if the slider isn't already moving then trigger the 'next' function
		 */

		if(moving) return false;
		next();
		
	});

	/**
	 * add the next button to the main sliding element
	 * then position it 5 pixels to the right of the slide buttons
	 */
	elem.appendChild(nextBtn);
	nextBtn.style.left = (parseInt(ul.style.left)+ul.offsetWidth+5)+"px";

	/**
	 * Create the previous button
	 * almost identical to the next button except positioned
	 * to the left of the slide buttons
	 * uses the next function when clicked with the direction set to 'previous'
	 */
	var prevBtn = document.createElement('a');
	prevBtn.href = "#";
	prevBtn.innerHTML = "<";
	prevBtn.className = "control left"
	prevBtn.addEventListener('click',function(e) {
		e.preventDefault();
		if(moving) return false;
		next('previous');
		
	});
	elem.appendChild(prevBtn);
	prevBtn.style.left = (parseInt(ul.style.left)-prevBtn.offsetWidth-5)+"px";

	/**
	 * Positions the next and previous buttons at the top of the slider
	 */
	var top = "5px";
	nextBtn.style.top = top;
	prevBtn.style.top = top;
}

The above code creates and positions the slider controls, a lot of this can be adjusted for personal preference, for example you might want to position your next and previous buttons to be against the right/left edges of the slider instead of at the top.

Now that we've created our buttons we need to finish off the 'buttonHndl' function, this function will allow us to move to a specific slide when one of the slide buttons is pressed.

/**
 * Event handler for control button presses
 * Param: e, event information
 */
function buttonHndl(e) {

	/**
	 * Stop the default behaviour of clicking a link
	 */
	e.preventDefault();

	/**
	 * Check the slider isn't already moving
	 */
	if(moving) return false;

	/**
	 * Get the element that was clicked
	 * We need the LI element containing the A tag, so travel up through
	 * the parent elements until we reach the LI
	 */
	var target = e.target;
	while(target.nodeName !== "LI") {
		target = target.parentElement;
	}

	/**
	 * Get the index of the LI element within it's parent UL
	 * pass that to the 'next' function so it can move to the
	 * slide with that index
	 */
	var index = target.parentElement.children.indexOf(target);
	next('next',index);	
}

The above function simply gets the index (the numerical position) of the clicked element, then passes that to the next function, the next function will then get the slide with the corresponding index and move that into view.

Timing

Now that all the elements of the slider have been put into place we need to get the slides moving, to do that we first need to create our Timer object.

This object will control when the slider automatically moves to the next slide and will also allow us to pause and resume the slider.

/**
 * Timer object used for controlling timing between slide movements.
 * Param: callback, function to call on each iteration of the timing loop
 * Param: delay, time in milliseconds between each loop
 */
 function Timer(callback, delay) {
    var timerId, start, delay;

	/**
	 * Stops the timeout
	 * effectively pausing the slider
	 */
    this.pause = function() {
        window.clearTimeout(timerId);
    };

	/**
	 * Starts an iteration
	 */
    var resume = function() {
        start = new Date();

        /**
         * Wait the amount of time specified by the delay variable
         * save the ID of the timeout so we can later clear it to pause
         */
        timerId = window.setTimeout(function() {
            
            /**
             * Call resume again to begin the next iteration
             * run the callback function
             */
            resume();
            callback();
        }, delay);
    };

    /**
     * Make resume publically accessible
     */
    this.resume = resume;

    /**
     * Begin first iteration of loop
     */
    this.resume();
}

Now that we've created the timer we can initiate the slider and add 'pause' and 'resume' functions to the slider instance. We can also add some mouse events to the slider to pause sliding when the mouse hovers over the slider.

/**
 * Create our timer and add it to the instance
 * we add the delay to the speed to give the slider chance to finish
 * sliding before the delay begins
 */
this.timer = new Timer(next,delay+speed);

/**
 * add pause and resume functions
 * to the slider instance
 */
this.pause = function() {
	this.timer.pause();
};
this.resume = function() {
	this.timer.resume();
};

/**
 * add mouse over and mouse out events to pause/resume the slider
 */
elem.addEventListener('mouseover',function() {
	that.timer.pause();
});
elem.addEventListener('mouseout',function() {
	that.timer.resume();
});
The Story So Far

Up to this point we've created a JavaScript object that takes an element containing a number of child elements and prepares them for use in a slider. A timer object has also been created which is continously running the 'next' function after a set interval.

If you were to use the object at this point you would get a slider stuck on its first slide, to get it moving we need to complete the 'next' function which will move the slider to the next slide.

Lets Get Moving

To get our slides to move we need to finish our 'next' function. This will involve figuring out which slide we're currently on, which slide we want to move to, then applying the appropriate classes to move the slides into position.

/**
 * Move to the next slide.
 * Param: direction, direction to move (optional)
 * Param: index, index of slide to move to (optional)
 */
function next(direction,index) {

	/**
	 * Set moving variable to disable controls
	 */
	moving = true;

	/**
	 * Set the direction, default to 'next'
	 */
	var direction = typeof direction == 'undefined' ? 'next' : direction;

	/**
	 * if we don't know which slide we're currently on 
	 * then get the first slide with the 'active' class
	 */
	if(current == null) {
		current = elem.getElementsByClassName('active')[0];
	}

	/**
	 * Check if a slide index has been supplied and that it's within the range
	 * of 0 and the total number of slides
	 */
	if(index !== "undefined" && index <= current.parentElement.children.length-1 && index >= 0) {

		/**
		 * check if the index given is less than the index of the current slide
		 * if it is then we're going backwards, so set the direction to 'previous'
		 *
		 * also check that the index given isn't the index of the current slide
		 */
		if(current.parentElement.children.indexOf(current) > index) {
			direction = 'previous';
		}  else if(current.parentElement.children.indexOf(current) === index) {
			return false;
		}

		/**
		 * get the next slide using the index
		 */
		var next = current.parentElement.children[index];
		
	} else {

		/**
		 * get the next slide by checking the sibling of the current slide
		 * if going to the 'next' slide then use nextElementSibling
		 * if going to the 'previous' then use previousElementSibling
		 * if the sibling doesn't exist then use the first or last slide depending on direction
		 */
		if(direction == 'next') {
			var next = current.nextElementSibling;
			if(next === null) {
				next = current.parentElement.children[0];
			}	
		} else {
			var next = current.previousElementSibling;
			if(next === null) {
				next = current.parentElement.children[current.parentElement.children.length-1];
			}
		}
	}
	if(controls) {

		/**
		 * If we have controls turned on then update the active button
		 * get the index of the next slide, set the button with that index as active
		 */
		var index = current.parentElement.children.indexOf(next);
		buttons.getElementsByClassName('current')[0].className = "";
		buttons.children[index].className = "current";
	}
	/**
	 * Getting the correct class set from the Slider.types object
	 */
	if(type == "random") {
		/**
		 * If we have the type set to 'random'
		 * generate a random number between 0 and the total number of types
		 */
		var rand = Math.floor(Math.random()*Object.keys(Slider.types).length);
		
		var i = 0;
		
		/**
		 * Loop through the slider types until
		 * i = our randomly generated number
		 * leaving sliderType as the key for the class set
		 * we need from Slider.types
		 */
		for(sliderType in Slider.types) {
			if(i == rand) {
				break;
			}
			i++;
		}
		/**
		 * Get the class for the slide entering the frame (inClass)
		 * and the class for the slide existing (outClass)
		 */
		var inClass = Slider.types[sliderType].in[direction];
		var outClass = Slider.types[sliderType].out[direction];
	} else {

		/**
		 * Get in/out classes for the chosen slide type
		 */
		var inClass = Slider.types[type].in[direction];
		var outClass = Slider.types[type].out[direction];
	}


	/**
	 * Apply class for incoming slide to the next slide
	 * at this point the slide will be out of frame
	 * but be ready to 'transition' into view
	 */
	next.className = "item active "+inClass;

	/**
	 * Set timeout to give class styles chance
	 * to update
	 */
	setTimeout(function() {

		/**
		 * Apply class to move current slide out of frame
		 */
		current.className = "item active "+outClass;

		/**
		 * Remove all classes from the next slide
		 * except for item and active, this should
		 * move the slide into frame
		 */
		next.className = "item active";

		/**
		 * Set a timeout that will execute
		 * after the slide has finished
		 * transitioning
		 *
		 * Removes the 'active' class from the previous
		 * slide, updates the 'current' variable with the new slide
		 * sets 'moving' to false reactiving controls
		 */
		setTimeout(function() {
			current.className = "item";
			current = next;
			moving = false;
		},speed);
	},100);
}

The above function gets called everytime the slider moves on to the next slide. It uses the classes defined in our slider types object to move the slides into the correct positions.

With this function in place our Slider class is complete, we can now use it to create a slider.

<script src='slider.js'></script>
<link rel='stylesheet' href='slider.css'>

<div id='slider_example' width='300px' height='230px'>
	<div>
		<h1>Slide 1</h1>
	</div>
	<div>
		<h1>Slide 2</h1>
	</div>
	<div>
		<h1>Slide 3</h1>
	</div>
</div>

<script>
	new Slider(document.getElementById('slider_example'),3000,500,true,'horizontal');
</script>