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 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 5 seconds we would write the following:

setInterval(someFunc, 5000);

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 5s = 5000ms.

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

Implementation

Before we jump into writing the automation 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, 5000);
// 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 5 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++; // incrementing the index
    navigateSlider(); // then navigate
}, 5000);

Executing this would result in an anonymous function being called after every 5s, 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 automation logic.

For instance, to change the interval's time (which is 5 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 timeInterval to hold setInterval()'s second argument.
  2. Create a global Boolean variable automate to indicate whether automation shall be done or not. If it's true, only then do we proceed with the code above.

Summarising both these ideas, we get to the following:

var timeInterval = 5000;
var automate = true;

if (automate) {
    setInterval(function() {
        newIndex++;
        navigateSlider();
    }, timeInterval);
}
Currently, we've preset automate 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 automation is needed. We'll see what this means later in this tutorial.

And this automates our simple slider ensuring that in the process we can, at any time, navigate from the current slide to the next or previous one without getting into any sort of errors.

Basic Autoplay Slider

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, let's say when the user is reading something within the current slide. However, just figuring out the fact of clearing the interval isn't enough — we have to also figure out the exact place where we would accomplish 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 automate and proceed only if it evaluates to true. In the automation 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 automation feature!

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

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

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

var timerID = null;

function navigateSlider() {
    if (automate) {
        // clear the ongoing interval
        clearInterval(timerID);

        // then start a new one
        timerID = setInterval(function() {
            newIndex++;
            navigateSlider();
        }, timeInterval);
    }

    // rest of the code
}
The last // rest of the code comment here is a placeholder for the segment inside navigateSlider() that we developed in the previous Pagination chapter. We've omitted it only to favour readability — you will obviously need it!

Inside navigateSlider(), first we perform a check for automation and proceed if it's desired (that is, automate is true). In automating the slider, we start by clearing the on-going interval (in line 6) and then setting a new one (in line 9).

The global timerID variable is created to hold the on-going timer. It's used in the clearInterval() function to remove the respective timer, and later on used to hold the ID of the newly set timer.

Now, since inside navigateSlider(), the timer is set and saved in the variable timerID, we would need to do the same thing in the if block, in the global scope.

var timeInterval = 5000,
     automate = true,
     timerID = null;

if (automate) {
    timerID = setInterval(function() {
        newIndex++;
        navigateSlider();
    }, timeInterval);
}

Note that it's desirable to bring up the declaration of the variable timerID before the execution the if statement above. Although this won't prevent any errors (unless you're running in strict mode), it's always good to have your variables defined before you work with them.

If you need to know how the function setInterval() works in JavaScript you can go to JavaScript Timers.

Dont Repeat Yourself (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().

var timeInterval = 5000,
     automate = true,
     timerID = null;

if (automate) {
    timerID = setInterval(function() {
        /* ... */
    }, timeInterval);
}
function navigateSlider() {
    if (automate) {
        clearInterval(timerID);

        timerID = setInterval(function() {
            /* ... */
        }, timeInterval);
    }

/* ... */
}

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() {
    timerID = setInterval(function() {
        newIndex++;
        navigateSlider();
    }, timeInterval);
}

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

var timeInterval = 5000,
     automate = true,
     timerID = null;

if (automate) {
    startTimer()
}
function navigateSlider() {
    if (automate) {
        clearInterval(timerID);
        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.

We will create a global object Automation and encapsulate all the functionalities for our automation 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 Automation to keep things well-structured and separate within their respective namespaces.

Following is the definition of Automation. Observe the name we've used for each of the methods and that how does it speak out the method's underlying purpose in a quick and precise manner.

var Automation = {
    // the id used to clear the interval
    id: null,

    // the time interval, in milliseconds
    // for now, we use a dummy value 5000
    timeInterval: 5000,

    // start a new interval
    start: function() {
        this.id = setInterval(function() {
            newIndex++;
            navigateSlider();
        }, this.timeInterval);
    },

    // reset the interval
    reset: function() {
        clearInterval(this.id);
        this.start();
    }
}

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

The property timeInterval is created in place of the global time variable we created before. Since time is something more related to the automation functionality, the Automation object serves to hold it rather than the global object.

Make sure to remove the global time 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 object in place, we can now rewrite our old code snippets using its methods:

var automate = true;

if (automate) {
    Automation.start();
}
function navigateSlider() {
    if (automate) {
        // reset the interval
        Automation.reset();
    }
    // rest of the code
}

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 automation 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!