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() {}
The buttons are selected via their class name 'slider_nav'
(in line 2) and then each one is assigned a click handler separately.
← 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?
One way to do this is 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:
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:
- A global variable to hold the index of the current slide.
- 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!
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:
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 - currentIndex
now points to the slide newly brought in.
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?
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?
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.
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.
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.