Introduction

In the previous chapter we constructed the HTML, as well as the CSS for our slider from scratch, and it is now that we will dig deeper into its last requirement i.e JavaScript.

Likewise, before you start on with this chapter make sure you are familiar with the previous one. Not only this but to be able to fully comprehend this chapter you ought to know JavaScript really well.

If you are not fluent in JavaScript, please consider taking our JavaScript Course.

Buttons and events

Recall that the HTML code in the previous chapter had two navigation buttons for moving across the slider - one for going left/backward and the other one for going right/forward.

Now we shall make both these buttons interactive.

So first thing's first, we'll start by selecting the buttons and then giving each a click event handler:

// select the navigation buttons
var sliderNavButtons = document.getElementsByClassName("slider_nav");

sliderNavButtons[0].onclick = function() {}
sliderNavButtons[1].onclick = function() {}
If you don't know what are event handlers, or the whole concept of events in JavaScript, then you can consider going over our JavaScript Events unit.

The buttons are selected via their class name 'slider_nav' (in line 2) and then each one is assigned a click handler separately.

Now what do you think shall happen when the ← Previous button is pressed? Similarly, what should happen when the Next → button is pressed?

The ← Previous button shall hide the current slide and show the previous one while the Next → button shall hide the current slide and show the next one.

Let's take an example. Suppose we have a slider with 3 slides. The page loads and the first slide is shown. The button is pressed and as a result the second slide is to be shown, while the first one is to be hidden..

This can be done very easily by setting the display property of the first slide's style object to "none" and setting the same property to "block" on the second slide.

In the end, the second slide is visible to the user. Now suppose that th enext button is pressed again. This time the second slide has to be hidden and the third slide has to be shown. The same property assignments would occur as in the previous case, but this time on the second and third slides respectively.

In short, whenever the Next → button is pressed, current slide is hidden and the next slide is displayed. The question is how to know which slide is the current slide?

How to know which slide is the current slide?

One way to do this is to:

Create a global variable that holds the index of the current slide. Initially, it's set to 0 to indicate that the first slide is currently shown (the first slide has index 0). On the click of the Next → button, it's incremented by 1.

For example, when the next button is pressed while the first slide is currently active, this variable gets incremeted to 1. This means that the second slide is now shown (the second slide has index 1).

Another way is to:

Create a global variable that stores a reference to the current slide. Initially this variable points to the first slide. On the click of the Next → button, it is set to the nextElementSibling of the current slide.

Now what to use?

Both methods are equally good if the slider only offers left-right navigation. However, if there is a pagination feature on the slider where the user could go to any slide he/she desires, then the former way would work a lot better. Here's how..

Imagine we have a slider containing 10 slides. The page loads and, as always, the first slide is shown. Now the user clicks the 10th pagination circle to navigate to the 10th slide directly.

If we go with the first choice i.e a global variable holds the index of the current slide, we can simply set the variable to 10.

However, if we go with the second choice i.e a global variable holds a reference to the current slide, then we would need to get the reference to the 10th slide by first obtaining a list of all slides and then accessing the 10th element from it.

As is clear, the first choice is better - a direct solution! Hence, we would go with it.

The things required are:

  1. A global variable to hold the index of the current slide.
  2. Another global variable to hold a list of all the slides.

We'll call the former currentIndex and the latter slides.

Consider the code below which defines both these new variables:

var currentIndex = 0;
var slides = document.getElementsByClassName("slider_slide");

var sliderNavButtons = document.getElementsByClassName("slider_nav");

/* ... */

Let's now see how to layout the click handlers of the ← Previous and Next → buttons.

To recap it - and this time in an elaborate way - when the ← Previous button is clicked, the current slide must be hidden and the previous slide must be shown. The currentIndex must also be decremented at the same time.

The same goes for the Next → button; except that we have to show the next slide and increment currentIndex when it is clicked.

Consider the code below:

sliderNavButtons[0].onclick = function() {
    // hide the current slide
    slides[currentIndex].style.display = "none";

    // decrement the index
    currentIndex--;

    // show the previous slide
    slides[currentIndex].style.display = "block";
}

sliderNavButtons[1].onclick = function() {
    // hide the current slide
    slides[currentIndex].style.display = "none";

    // increment the index
    currentIndex++;

    // show the next slide
    slides[currentIndex].style.display = "block";
}

In the handler function for sliderNavButtons[0] (from line 2 - 9), we start by hiding the current slide using currentIndex. Then we decrement currentIndex to point to the previous slide. Finally we show the previous slide using this new decremented value of currentIndex.

The same goes for the sliderNavButtons[1], apart from that it increments currentIndex.

With all this, we have a working slider!

Simple Interactive Slider

However it is not without errors and is therefore not a perfect slider. Next we will see how we can improve this slider code by removing these errors and coming under the hood of best practices.

DRY (Don't Repeat Yourself)

If you notice, in the code above, we are merely repeating statements that show and hide the slides. We've highlighted the concerned statements below:

sliderNavButtons[0].onclick = function() {
    slides[currentIndex].style.display = "none";
    currentIndex--;
    slides[currentIndex].style.display = "block";
}

sliderNavButtons[1].onclick = function() {
    slides[currentIndex].style.display = "none";
    currentIndex++;
    slides[currentIndex].style.display = "block";
}

Whenever developing programs, the DRY principle shall be in mind:

We should keep from repeating code in programs, as much as we can. Instead of repeating stuff, we should create functions, put the repeating code in them and ultimately use these functions.

This makes our programs extensible and much easier to maintain.

So going back to our slider's script, we will now create a navigateSlider() function and put the repeating statements to navigate the slider within it.

And to get this function to work correctly we'll need another global variable, newIndex, to keep track of the new slide, as can be seen below:

var currentIndex = 0;
var newIndex = 0; // keep track of the new slide

var slides = document.getElementsByClassName("slider_slide");
var sliderNavButtons = document.getElementsByClassName("slider_nav");

function navigateSlider() {
    slides[currentIndex].style.display = "none";
    slides[newIndex].style.display = "block";
    currentIndex = newIndex;
}

sliderNavButtons[0].onclick = function() {
    newIndex--;
    navigateSlider();
};
sliderNavButtons[1].onclick = function() {
    newIndex++;
    navigateSlider();
}

In the navigateSlider() function above, we first hide the slide at position currentIndex and then show the one at position newIndex.

This follows from the fact that currentIndex holds the index of the current slide, which needs to be hidden; whereas newIndex holds the index of the new slide, which needs to be shown.

After getting this done we set currentIndex equal to newIndex. This indicates that the current slide of our slider has changed - currentIndexnow points to the slide newly brought in.

Live Example

Dealing with extreme slides

In the code above we haven't dealt with cases where the extreme slides, or in other words the first and last slides, are reached. In these cases we at least don't want to increment or decrement newIndex because doing this can throw errors.

How?

At the start we have newIndex = 0, and therefore pressing the ← Previous button will decrement it to -1. Consequently the statement slides[newIndex].style.display = "block"; will throw an exception since there's no newIndex such as -1!

Similarly we have newIndex = 2 at the last slide, and therefore pressing the Next → button will increment it to 3. Consequently the statement slides[newIndex].style.display = "block"; will once again throw an exception since there's no index such as 3 (only 0, 1 and 2)!

So what we can do instead is use conditional statements to check for these extreme positions and then either repeat the cycle or break the flow.

Let's see what both these mean.

Repeat the cycle

Suppose that someone is on the last slide and at this point presses the forward button. In this repeat-the-cycle behaviour what we will do is take him to the first slide again, and thus repeat the navigation cycle.

In this way the slider will keep on navigating with the forward button, without any bound.

The same would also apply for the backward button on the first slide - in this case we would repeat the cycle by showing the last slide, and obviously hiding the first one.

But how do we check for these extreme cases in code? For a hint, these cases will be accomplished by newIndex - it will be equal to some value specifically signifying the first and last slides. So did you get it?

For the first slide newIndex would be 0 and for the last one it would be the number of slides - 1.

We can layout this check for newIndex within our navigateSlider() function, and apply a behaviour according to the value.

With all this theory in place we arrive at the following code:

var slidesLength = slides.length; // number of slides

function navigateSlider() {
    // check for extreme slides
    if (newIndex === -1) newIndex = slidesLength - 1;
    else if (newIndex === slidesLength) newIndex = 0

    slides[currentIndex].style.display = "none";
    slides[newIndex].style.display = "block";
    currentIndex = newIndex;
}

We have different checks for different handlers - for Previous we check when the first slide has reached and for Next we check when the last slide is reached. The variable slidesLength, holding the total number of slides, is used in determing the last slide's index.

repeat-the-cycle slider

Break the flow

The second option we have for the extreme slides case is to break the flow.

As the name implies it will simply stop the navigation when the slider reaches the first or last slides, unlike the first choice where we can navigate infinitely.

Visually this would mean giving the buttons a disabled look, which can be done by assigning the disabled attribute and styling its :disabled psuedo state with a low value of opacity.

The following code accomplishes this task:

.slider_nav:disabled {
    /* make the button almost invisible */
    opacity: 0.4
}

Although many browsers automatically reduce the opacity on disabled input fields and buttons, the CSS code above goes even beyond that for an even lower level of opacity.

var slidesLength = slides.length; // number of slides

function navigateSlider() {
    if (newIndex === -1) newIndex = 0;
    else if (newIndex === slidesLength) newIndex = slidesLength - 1;

    sliderNavButtons[0].disabled = (newIndex === 0) ? true : false;
    sliderNavButtons[1].disabled = (newIndex === slidesLength - 1) ? true : false;

    slides[currentIndex].style.display = "none";
    slides[newIndex].style.display = "block";
    currentIndex = newIndex;
}

Here we, once again, first rectify the extreme cases and normalize newIndex to 0 when it becomes -1; and to slidesLength - 1 when it becomes equal to slidesLength.

Additionally we also check when the index is 0 (first slide shown) or slidesLength - 1 (last slide shown), and disable the backward or forward buttons correspondingly. When newIndex changes from either of these values, we enable the respective button.

break-the-flow slider

Moving on

With all this we now have, as we said earlier, a perfect slider working flawlessly. Add as many number of .slides you like and the slider will scale according to them.

Well despite the fact that this slider was a simple one, it arguabaly had a lot of details to deal with, near overwhelming, didn't it? So you can surely relate to the fact yourself, that what is coming up next will be much difficult and detailed than this simple slider's simple working!!

In the coming chapters we will move on to add numerous features to our slider including the legacy swiping interaction which would be literally 10 times as much information on this page, and even 10 times more fun!

To keep things in order, let's go over adding a simple feature to our slider that is pagination in the next chapter.