Slider Pagination

Chapter 4 15 mins

Learning outcomes:

  1. What is pagination
  2. The pagination container .slider_pagination
  3. Creating pagination buttons using JavaScript
  4. Giving interactivity to the buttons

What is pagination?

It's highly likely that you're already familiar with the concept of pagination, without really knowing it.

When you search for something on a search engine such as Google, a list of suggestions appears. Down this list, or sometimes even at the top of it, there is a whole string of numbers mentioned, starting from 1. Each of these numbers points to a given page of the results.

An example follows:

<123>
Pagination on a search engine.

For instance, 1 would point to page 1 of the list of suggestions; similarly, 2 would point to page 2 of the list of suggestions, and so on.

This is pagination.

Its purpose is to break down content into a list of pages each of which can be visited by pressing the corresponding element in the pagination block.

Pagination is also used extensively on mobile phone operating systems. For example, on Android, there is a sequence of circles near the bottom of the home page that represents the total number of thumbnails on the phone. At any given instant, a specific circle is filled to indicate that the corresponding slide is currently shown.

Thumbnail pagination on some mobile OS.

Anyways, moving on, in this chapter we shall implement the idea of pagination in our slider. By paginating our slider, we'll accomplish two things:

  1. Make the user aware of the fact that how many slides are there in a slider.
  2. Make navigation a lot more quicker. For instance, to navigate to the 10th slide, the user would only have to click the 10th pagination element.

With the explanation out of the way, it's time to move on to the coding part.

The pagination container

As can be seen in the pagination illustrations above, we need to have a container to hold the individual pagination buttons (shown as numbers, or circles, or even squares).

The container will be simply another <div>, which we'll call .slider_pagination.

Now where to put this element in our markup?

This is a very good question. Let's deal with it logically.

We have two main containers, out of which we need to choose one and put .slider_pagination directly in that. They are .slider, .slider_slides-cont.

Clearly, .slider_slides-cont isn't the right choice as it's only meant to hold the slides of the slider. This leaves us with one option and that is .slider. Hence, we put .slider_pagination directly inside .slider, and that immediately after .slider_slides-cont.

Shown below is the markup:

<div class="slider">
   <div class="slider_slides-cont">
      <div class="slider_slide"><img src="pexels-stijn-dijkstra-2499786.jpg"></div>
      <div class="slider_slide"><img src="pexels-addie-3152128.jpg"></div>
      <div class="slider_slide"><img src="pexels-nextvoyage-3520548.jpg"></div>
   </div>
<div class="slider_pagination"></div> <button class="slider_nav">&larr; Previous</button> <button class="slider_nav">Next &rarr;</button> </div>

Now that the pagination container is there, it's time to see how to create the individual pagination buttons in it.

Creating the pagination buttons

Let's say we have 10 slides in our slider.

How many pagination buttons would we need to create?

That's right — 10. In general, as many pagination buttons ought to be created as many slides are there.

But how to create and put these pagination buttons inside .slider_pagination?

How to create and put the pagination buttons inside the .slider_pagination element?

We need a loop with the number of iterations equal to the number of slides. In each iteration of the loop, a pagination button is created and put inside .slider_pagination.

And here comes another question. Coding is just full of questions!

How to create a pagination button in each iteration?

There are two ways to create a pagination button in each iteration of the for loop:

  1. Create a string containing the HTML markup of the button, to be later put into the innerHTML property of .slider_pagination.
  2. Create an HTMLButtonElement node representing the button, to be later put inside the .slider_pagination element.

We'll go with the former choice i.e. the one that relies on the innerHTML property. This is because it's shorter and easier to set up.

Keep performance in mind!

One thing worth mentioning over here is that, in choice 2, it's inefficient to add each of the HTMLButtonElement nodes created to the DOM one-by-one. This is because doing so causes frequent reflows (layout recalculations made by the browser), negatively affecting performance.

A better approach, if we go with choice 2, is to create add each created node to a document fragment, and then in the end finally dump everything stored in this fragment into the DOM. This only causes one reflow, compared to the multiple reflows of the naive approach.

First thing's first, let's select the .slider_pagination element so that we can easily add stuff to it in our script:

var currentIndex = 0;
var newIndex = 0;

var slideElements = document.getElementsByClassName('slider_slide');
var slidesLength = slideElements.length;
var paginationElement = document.getElementsByClassName('slider_pagination')[0]; var navElements = document.getElementsByClassName('slider_nav'); function navigateSlider() { /* ... */ } navElements[0].addEventListener('click', function() { /* ... */ }); navElements[1].addEventListener('click', function() { /* ... */ });

With this done, the next step is to create a for loop in order to fill the pagination container with pagination buttons:

/* ... */

var paginationHTML = [];
for (var i = 0; i < slidesLength; i++) {
   paginationHTML.push('<button class="slider_pagination_btn"></button>');
}
paginationElement.innerHTML = paginationHTML.join('');

The paginationHTML variable serves to hold the HTML markup to be dumped inside the pagination container .slider_pagination. It's set to [] at the start so that each button's HTML can be stacked up in this array and in the end joined up together at once.

Using a string value instead, would require concatenation in each iteration which is comparatively a more expensive operation than appending items into an array and joining them at once after all the iterations complete.

Styling the buttons

We want the pagination buttons to look like real pagination buttons. It's now that we shall accomplish this requirement.

First of all, we'll make each button look like a circle. Then we'll give each one a :hover rule. Lastly, we'll give the currently selected circle, which is denoted via the class .slider_pagination_btn--sel, a filled background to indicate that it is currently active.

And this is it!

On a separate HTML page we write the following markup along with the CSS shown, just so that we can test how our pagination buttons are looking before taking them to the slider:

<div class="slider_pagination">
   <button class="slider_pagination_btn"></button>
   <button class="slider_pagination_btn"></button>
   <button class="slider_pagination_btn"></button>
</div>
.slider_pagination_btn {
   display: inline-block;
   padding: 6px;
   margin: 5px;
   background-color: #ccc;
   border-radius: 10px;
   cursor: pointer;

   /* override default button styles */
   border: none;
   outline: none;
}
.slider_pagination_btn:hover {
   background-color: #aaa;
}
.slider_pagination_btn:focus {
   background-color: #777;
 }
Instead of giving padding: 6px you can also give both the declarations width: 12px and height: 12px. But if you do this, keep in mind that you'll need to set padding: 0 in order to remove the default user-agent padding on buttons.

Although the pagination button looks exactly like we want to, there is a hidden issue in it that'll likely reduce the UX of the slider.

That is, the clickable region of each pagination button is very small and very precise. In other words, we have to take the mouse pointer exactly within the visual circle to activate its hover style.

We can certainly improve this by using some CSS tricks.

In the following code, we use the :before pseudo-element on the pagination button to create the actual pagination circle and then use the button element itself to apply padding around the circle. In this way, the circle would look the same as before but would now have a larger clickable region and thus improve the UX of the slider.

.slider_pagination_btn {
   display: inline-block;
   padding: 6px;
   cursor: pointer;

   /* Override default button styles */
   border: none;
   outline: none;
   background-color: transparent;
}

/* Actual pagination circle */
.slider_pagination_btn:before {
   content: '';
   display: inline-block;
   padding: 6px;
   border-radius: 100%;
   background-color: #ccc;
}

.slider_pagination_btn:hover:before {
   background-color: #aaa;
}

.slider_pagination_btn:focus:before {
   background-color: #777;
}

Each style declaration here has a purpose, which won't be difficult to understand given that you are well-versed in CSS.

Anyways, moving on, let's now also test the .slider_pagination_btn--sel class to see how it looks:

<div class="slider_pagination">
   <button class="slider_pagination_btn"></button>
   <button class="slider_pagination_btn slider_pagination_btn--sel"></button>
   <button class="slider_pagination_btn"></button>
</div>
.slider_pagination_btn.slider_pagination_btn--sel:before {
   background-color: black
}

See how the darker background color gets emphasis placed on the currently selected button.

The reason of including .slider_pagination_btn in the selector used above is to prevent the previous :hover and :focus selectors from overriding the styles of .slider_pagination_btn--sel.

Now, since the styles of the pagination are OK with us, we finally put them into the slider.

Here's a live example of a slider with pagination.

Live Example

Giving interactivity

When a given pagination button is clicked, the slider ought to be navigated to that very slide. This means that there needs to be a click handler working on these buttons.

That whether this handler needs to be on each button or on the .slider_pagination element is to be sorted through discussion.

So there are two ways to make the slider's pagination buttons interactive:

  1. Give a click handler to each button.
  2. Give a click handler to .slider_pagination and check whether the target of the click is a button.

What do you think would be more efficient? Remember, efficiency shall always be in mind.

Well, choice 2 is apparently more efficient.

Why? Simply because it requires only one click handler per a slider. In contrast to this, choice 1 requires as many click handlers as the number of slides.

Keeping the total number of handlers in an application as little as possible is always a good thing to do. It saves memory and keeps the code base readable, usually.

Alright, so within the click handler of .slider_pagination, we retrieve the target of the event and see if it has the class "slider_pagination_btn" set on it. If it has, we know that we have clicked a button and likewise bring its corresponding slide into view.

But how do we know which button corresponds to which slide?

Trivially, the 1st button would correspond to the 1st slide, the 2nd one would correspond to the 2nd slide, the 3rd to the 3rd slide, and so on. The question is that how do we get our code to determine this?

How does the code know which slide corresponds to which pagination button?

Well, we can either:

  1. Put an attribute on each button to hold its index, which is same as the index of the corresponding slide.
  2. Save the corresponding slide's reference on each button node.

Once again, a battle of two choices to accomplish a particular task.

Let's see who wins?

Well, choice 1 wins this time. Saving the index on each button is way more flexible than saving the corresponding slide's reference.

Why?

Later, in the effects chapter we would need to move .slider_slides-cont completely by a given value when a given .slider_pagination button is clicked. Having the index on each button would aid us in quickly calculating the amount by which the container would need to be moved to get the corresponding slide into view.

So, going with choice 1, let's first change our previous code that creates the pagination buttons, to include a string in it that sets the data-index attribute on each pagination button:

/* ... */

var paginationHTML = [];
for (var i = 0; i < slidesLength; i++) {
   paginationHTML.push('<button class="slider_pagination_btn" data-index="' + i + '"></button>');
}
paginationElement.innerHTML = paginationHTML.join("");

After this, the second thing to be done is to give a click listener to the .slider_pagination element.

This is accomplished as follows:

paginationElement.addEventListener('click', function(e) {
   var target = e.target;
   if (target.classList.contains("slider_pagination_btn")) {
      newIndex = Number(target.getAttribute("data-index"));
      navigateSlider();
   }
});

When .slider_pagination is clicked, it's first checked whether the target of the click is a pagination button. If it is a button, we retrieve the value of its data-index attribute, convert it to a number (since it's originally a string), assign it to the variable newIndex and finally navigate the slider.

A functional, paginated, slider awaits your view!

Live Example

Filling the selected button

If you notice in the live example above, when the slider navigates to a new slide, the corresponding pagination button is NOT filled/highlighted, which is all the essence of pagination.

We should know exactly at which slide we are, by looking at the pagination buttons!

Let's now get this dealt with too.

To do the highlighting part we just ought to alter our previous navigateSlider() function slightly and get the job done in the span of seconds.

Whenever the slider is navigated to a new slide (using the navigateSlider() function), the corresponding pagination button gets the class .slider_pagination_btn--sel added, whereas the previously filled button gets the same class removed.

To access the ith pagination button we refer to the ith child node of the .slider_pagination element in our script.

Consider the following code:

function navigateSlider() {
   navElements[0].disabled = (newIndex === 0);
   navElements[1].disabled = (newIndex === slidesLength - 1);

paginationElement.childNodes[currentIndex].classList.remove('slider_pagination_btn--sel');
paginationElement.childNodes[newIndex].classList.add('slider_pagination_btn--sel'); slideElements[currentIndex].style.display = 'none'; slideElements[newIndex].style.display = 'block'; currentIndex = newIndex; }

Now when we click on any button, it gets highlighted and the slider gets navigated to the corresponding slide. Perfect!

Paginated Slider

And we are all done with our pagination feature — it has the look to it and even functionality.