Slider Autoplay

Chapter 5 15 mins

Learning outcomes:

  1. What is meant by autoplay
  2. Using setInterval() for Autoplay
  3. Preventing unexpected slider navigations
  4. Following the DRY principle
  5. Grouping all functionalities under Autoplay

What is autoplay?

Have you ever come across a slider on a website which navigates automatically? A slider where you don't have to press any button whatsoever to go from one slide to another — it all happens on itself.

Don't you think it is amazing to have this autoplay feature on a slider? Perhaps it would've been even more amazing if we could somehow incorporate this idea into our very own slider and this is what this chapter is all about.

How it works?

The whole idea behind slides navigating automatically is to call a function that navigates them, repeatedly, after a certain time interval.

In our case the function is navigateSlider() and so what we are left with is only to figure out a way to repeatedly invoke this function.

Can you think of a way?

If you've read the chapter on JavaScript Timers, you would definitely have an answer right away.

The easiest way to execute a function repeatedly is to use the global method setInterval().

setInterval() takes two arguments — the first one is the function to be called repeatedly and the second one is the time interval between the calls (given in milliseconds).

For example, if we wanted to call a function someFunc() after every 4 seconds we would write the following:

setInterval(someFunc, 4000);

First we provide the function someFunc which has to be called repeatedly, and then the time interval as a number, in milliseconds.

Note that 1s = 1000ms, therefore 4s = 4000ms.

Now that you know how to call a function repeatedly, it's time to work on enabling the autoplay feature in our slider!

Implementing the feature

Before we jump into writing the Autoplay code, let's recall what needs to be done in order to perform a forward navigation of the slider.

First, newIndex has to be incremented, and then navigateSlider() has to be called.

If you aren't familiar with the variable newIndex in our slider script, head over to Slider Basics to understand its origins and purpose.

The same would also need to be done in conjunction with setInterval(), but at least not by passing the function navigateSlider directly as shown below:

setInterval(navigateSlider, 4000);
// newIndex++ is missing!

Here we've skipped the incrementing expression of newIndex, that actually enables the slider's navigation. Writing this code would mean navigating to the same value of newIndex after every 4 seconds — clearly not what we want!

What we ought to do instead, is to create another function, add both these statements within it, and finally pass it to setInterval(). This simple technique will be sufficient to solve our simple problem!

Now whether the function shall be named or anonymous is your choice — we'll go with the latter.

Following is the correct code:

setInterval(function() {
   newIndex++;
   navigateSlider();
}, 4000);

Executing this would result in an anonymous function being called after every 4s, which increments newIndex and then invokes navigateSlider().

Now despite the fact that this code works well, it doesn't provide us much of a control over the entire Autoplay logic.

For instance, to change the interval's time (which is 4 seconds in the case above), we have to come up at the location of the invocation of setInterval() and then make the alteration there. Furthermore, to disable the functionality of autoplay, we only have the option to comment this code out, and nothing else.

When developing applications, libraries, components like these, or simple anything in JavaScript, we have to think in an efficient and extensible way — one which makes it easy for us to go over the code and make given changes quickly.

For the case above, we can make two basic additions to it to give it a lot more flexibility:

  1. Create a global variable autoplayInterval to hold setInterval()'s second argument.
  2. Create a global Boolean variable enableAutoplay to indicate whether Autoplay shall be done or not. If it's true, only then do we proceed with the code above.

Summarizing both these ideas, we get to the following:

/* ... */

var enableAutoplay = true;
var autoplayInterval = 4000;

if (enableAutoplay) {
   setInterval(function() {
      newIndex++;
      navigateSlider();
   }, autoplayInterval);
}
Currently, we've pre-set enableAutoplay to true to get things working. However, in a real scenario we'll leave this is upto the developer using the library, to explicitly specify, somewhere in his code, whether Autoplay is needed. We'll see what this means later in this tutorial.

And this enables autoplay on our simple slider.

Live Example

Simple, wasn't it?

A slight problem...

Although this code works perfectly, there is a slight problem in it.

If the user is interacting with the slider, for example clicking the navigation buttons, we ideally want to clear the current interval and start a new one. In other words, we want to reset the timer.

This is to prevent any unexpected navigations from happening when the user is, let's say, reading something within the current slide or trying to interact with it.

So how to solve this?

We leave this as a quick, short exercise for you to do on your own and try to come up with a location in our previously developed script where the logic for clearing the interval can be set up.

Once you're done with this exercise, continue reading below.

Perhaps the best place to clear the ongoing interval is inside navigateSlider().

A couple of reasons contribute to this:

  1. Each time we make a navigation — be it using the navigation buttons, key strokes (as we shall see later in this tutorial), touch gestures etc. — we want to reset the on-going timer to start all over again. Since all these events call navigateSlider(), therefore it makes sense to add the interval logic directly within it.
  2. There's no point of adding this logic separately inside the handlers for all the events discussed in point 1). It will lead to less maintainable code and even lead to a lot of repetition — one of the worst things that can be present in code.

Sifting navigateSlider() for the implementation details of this feature, we see that first we need to check for enableAutoplay and proceed only if it evaluates to true. In the Autoplay logic, we need to clear the on-going interval using clearInterval() and then set another using setInterval().

Had we done this the other way round i.e by first setting the timer and then clearing it, we would've actually broken the Autoplay feature!

By first calling clearInterval() we ensure that the ongoing timer is put to a halt and then, by calling setInterval(), we ensure that a new one is begun for the same time interval.

You'll see this clear-then-set combination used quite often in applications for similar use cases, all boiling down to resetting a timer.

So let's finally incorporate this idea into our navigateSlider() function:

var autoplayTimer = null;

function navigateSlider() {
   if (enableAutoplay) {
      // Reset the timer.
      clearInterval(autoplayTimer);
      autoplayTimer = setInterval(function() {
         newIndex++;
         navigateSlider();
      }, autoplayInterval);
   }

   /* ... */
}

Inside navigateSlider(), first we perform a check for Autoplay and proceed if it's desired (that is, enableAutoplay is true). Inside the conditional, we simply reset the ongoing timer, which is held in the variable autoplayTimer.

One more thing to change is inside the conditional statement checking enableAutoplay in the global scope. Specifically, when setInterval() is called, we ought to save its return value inside the same autoplayTimer variable that we created above.

Below shown is the complete code:

/* ... */

function navigateSlider() { /* ... */ }

/* ... */

var enableAutoplay = true;
var autoplayInterval = 4000;
var autoplayTimer = null;

if (enableAutoplay) {
   autoplayTimer = setInterval(function() {
      newIndex++;
      navigateSlider();
   }, autoplayInterval);
}

Live Example

Time to DRY

If you are a believer of the DRY (Don't Repeat Yourself) principle in programming, then you would've surely picked up one problem in the code above.

It involves repetition!

Notice that the code to set an interval appears twice — first within the outer scope of our script, and then inside the definition for navigateSlider().

/* ... */

if (enableAutoplay) {
   autoplayTimer = setInterval(function() {
      /* ... */
   }, autoplayInterval);
}
function navigateSlider() {
   if (enableAutoplay) {
      clearInterval(autoplayTimer);
      autoplayTimer = setInterval(function() {
         /* ... */
      }, autoplayInterval);
   }

   /* ... */
}

The code present in both these locations is exactly the same and therefore must be grouped into one single unit.

Whenever we encounter repetitive pieces of code in a program, we must try our best to come up with a way to group them together in a function, and use that function instead.

In our case, we can create a function startTimer(), put the interval-setting logic in it, and then use this function in both the locations highlighted above.

Following we define the function:

function startTimer() {
   autoplayTimer = setInterval(function() {
      newIndex++;
      navigateSlider();
   }, autoplayInterval);
}

Now, let's call this in the desired places:

/* ... */

if (enableAutoplay) {
   startTimer();
}
function navigateSlider() {
   if (enableAutoplay) {
      clearInterval(autoplayTimer);
      startTimer();
   }

   /* ... */
}

Grouping all functionalities

Although the definition of startTimer() did solve our repetition problem above, it gave a slight awkward look to our overall script. There is one standalone function (startTimer()) doing a special thing, while rest of the things are being done directly.

For instance, the interval is cleared by directly calling clearInterval()NOT by calling some function such as clearTimer().

This sort of a design lacks structure, extensibility and efficiency. One has to scrutinize through the code to understand what exactly is happening — the code doesn't communicate this itself.

We shall now address this concern, and make our autoplay code more structured.

To start with, we'll create a global object called Autoplay and encapsulate all the functionalities for our Autoplay feature within it.

These functionalities are:

  1. Holding the time interval, in milliseconds.
  2. Setting an interval using setInterval().
  3. Clearing the interval using clearInterval().
  4. Saving the interval's ID to be passed to clearInterval().

So in simple terms, rather than creating multiple functions like startTimer() or clearTimer() in the outer scope, we'll create them under the object Autoplay to keep things well-structured and separate within their respective namespaces.

Following is the definition of Autoplay:

var Autoplay = {
   timerId: null,
   interval: 4000,

   start: function() {
      this.timerId = setInterval(function() {
         newIndex++;
         navigateSlider();
      }, this.interval);
   },

   reset: function() {
      clearInterval(this.timerId);
      this.start();
   }
}

Observe the name we've used for each of the methods and that how does it convey the method's underlying purpose in a quick and precise manner.

The property timerId is given to temporarily hold the ID returned by setInterval() to be passed to clearInterval() for clearing the respective interval.

The property interval is created in place of the global autoplayInterval variable we created before.

Make sure to remove the global autoplayInterval variable from your code!

The method start() solves the repetition problem we were discussing previously and serves to simply start a new interval.

Finally, the method reset() does the job we desired within navigateSlider() earlier i.e. first it clears the ongoing interval and then starts a new one. In other words, it resets the interval.

You can also create a method stop() here, to clear the interval instead of calling clearInterval() directly — it's all your choice! We've skipped it because it would've otherwise introduced more identifiers into our program, for no special reason!

With this Autoplay object in place, we can now rewrite our old code snippets using its methods:

var enableAutoplay = true;

if (enableAutoplay) {
   Autoplay.start();
}
function navigateSlider() {
   if (enableAutoplay) {
      Autoplay.reset();
   }

   /* ... */
}

Structured Autoplay Slider

In conclusion

And this marks an end to our slider's autoplay feature.

Some of you might be thinking that we could've also used setTimeout() for the Autoplay task above and that is absolutely alright. However, setTimeout() would've compromised on simplicity.

We would've had to recursively call setTimeout() inside a named function (instead of the anonymous function above in setInterval()) which would've been a mess if compared to the simple and easy setInterval() approach discussed above.

Remember the principle KISS - Keep It Simple Stupid — and you'll be all good in programming!