Introduction

Amongst the most difficult concepts involved in building a Javascript slider is adding touch interaction to it. Considering the browser compatibility charts for touch events, the ease to understand their functionality, coupled with ideas such as swipe speed, swipe direction and so on, it turns out that giving a touch interaction to a slider can be a daunting task - at least for beginners.

Experienced programmers out there, with good JS skills and thinking, however might not have that of a difficult time implementing it in real code.

So let's begin with understanding how this interactivity would work from the crust to the core.

Understanding the swipe

You are, with over than a 90% of probability, already familiar with touch based navigation from your phone's home page drawer - how do you go back and forth between the thumbnails by swiping your finger across the screen? When does a thumbnail slide in? When does it return back to its original position after you leave off the screen?

Try to answer these questions and you'll see yourself how to layout the code for a touch based slider!

Let's rigorously go together into these details operating under the hood.

Basically if you swipe your finger pretty swiftly across the screen you are navigated to a new slide/page/thumbnail, whatever your want to call it. In addition to this, even if you swipe slowly but beyond the half way point of the slide, you are again navigated to the new slide.

Therefore the two conditions: a fast swipe OR a half-way swipe cause a navigation to be made as soon as we release our touch pointer.

The other conditions would then obviously constitute a failure and hence cause the slide to return back to its original position. For instance if we move slowly lesser than the half way point we will be returned back.

And this is just what we experience all the time while swiping across our drawer's thumbnails without ever really noticing it so closely!

So what we conclude from this working is that for fast and half-way swipes a navigation should be made whereas for the other cases it shouldn't. This is the understanding we will base our touch interactive slider on to begin with.

We'll start off with simpler problems before moving on to the final complicated one.

So let's start off doing some solid work!

Beyond this point we are assuming that you are familiar with Javascript touch events specifically touchstart, touchend and touchmove. If you somehow aren't comfortable with them, then first consider understanding them.

Building the interaction

As always it's best to first get you to think on a problem instead of us simply giving away its solution. In this way you understand things better and for the longer run become able to solve even more complex problems, very easily.

So let's see where you stand in your Javascript skills!

How can we detect whether the swipe made was a swift one or a slow one?
Use the Date() constructor
Use the Time() constructor
Use JQuery's swipe event
How exactly can we use the Date() object in determining the speed of swipe. Choose the best answer.?
Calculate the time spent between touchstart and touchend
Use the Time() constructor

The first thing that we will begin with is determining the speed of swipe by using the Date() object to our advantage.

The idea is that we will calculate the time it took between touchstart (when the finger starts interacting) and touchend (when it's lifted up off the screen) and use this to determine the swipe's speed. If this time is fairly low, against a benchmark value then the swipe is fast and vice versa.

You can choose any benchmark value you like to but just make sure it's a fairly low one.

Following we implement this idea to determine swipe speed:

var startTime = endTime = 0;
var benchmarkTime = 200; // time in milliseconds

slidesWrapper.addEventListener("touchstart", function() {
    startTime = new Date();
});

slidesWrapper.addEventListener("touchend", function() {
    endTime = new Date();
    if ((endTime - startTime) < benchmarkTime) {
        // fast swipe
    } else {
        // slow swipe
    }
});

The two variables startTime and endTime hold the starting and ending times respectively in the touchstart and touchend event handlers. Then in line 10's conditional (highlighted above) we calculate their difference, compare it against our benchmark and then construct the two situations - fast swipe and a slow swipe. So simple, wasn't it?

The second thing now on the list is to determine whether the slide was swiped beyond its half-way point, given that the swipe was slow. In other words we have to track how much swiping have we done and compare it against half of our slide's width.

Can you figure out how can we get this done? Any ideas on event objects and pointer co-ordinates?

We will be needing the clientX property on the changedTouches[0] object and calculate the difference between its initial and final values. The initial value goes in touchstart whereas the final value goes in touchend.

Consider the following questions arising in the minds of thoughtful developers.

Why do we use changedTouches[0], and not targetTouches[0] or simply touches[0]? This is because we want only any moving touch pointer to cause the slider to move and this can most easily be accomplished using the changedTouches object.

Why do we use clientX, and not pageX? This is just based on preference - you can also use pageX since we are merely concerned with calculating a difference.

Having successfully solved a part of the problem by getting the amount of swiping done, we still are left with getting the value of half our slide's width. This can be done by dividing the width of the slider by 2. The method getBoundingClientRect() will help us in doing so.

The global variable halfSlideWidth serves to hold just this value in the code below:

var startTime = endTime = 0;
var benchmarkTime = 200; // time in milliseconds
var startPos = endPos = 0;
var halfSlideWidth = slides[0].getBoundingClientRect().width / 2;

slidesWrapper.addEventListener("touchstart", function(e) {
    startTime = new Date();
    startPos = e.changedTouches[0].clientX; // initial position
});

slidesWrapper.addEventListener("touchend", function(e) {
    endTime = new Date();
    endPos = e.changedTouches[0].clientX; // final position
    if ((endTime - startTime) < benchmarkTime) {
        // fast swipe
    } else {
        // slow swipe
        if ((endPos - startPos) >= halfSlideWidth) {
            // halfway swipe
        } else {
            // short swipe
        }
    }
});

From line 18-22, we utilise halfSlideWidth to determine whether we swipe beyond the half width of the slide.

Moving forward, now since the actual width of the slides can change with resizing the browser, following we add a resize event to counter this by recalculating halfSlideWidth.

The function setTimeout() is given to prevent the resize event from running too frequently as the browser window resizes - it simply throttles the recalculation of halfSlideWidth.

var resizeTimeout;
window.onresize = function() {
    clearTimeout(resizeTimeout);

    resizeTimeout = setTimeout(function() {
        // recalculate the slides's half width
        halfSlideWidth = slides[0].getBoundingClientRect().width / 2;
    }, 500);
}

So at this point we have succesfully layed out the code to test for a fast swipe OR halfway swipe (in the case of a slow one). Now we are only left to navigate our slider once we leave off the touch screen.

A solution that comes to the mind is to write navigateSlider() in the conditional block for a fast swipe (in line 15 above) and a halfway swipe (in line 19 above) and get done. But notice a slight problem in this.

With this code our slider won't work as expected - we couldn't ever navigate forwards to any slide from the first slide using a swipe interaction. Can you figure out why does this happen?

We have only specified navigateSlider() but no incrementing or decrementing expression for the counter variable on which it operates. Recall from the Simple Slider chapter that our navigation buttons first incremented/decremented counter and then called navigateSlider(). A similar approach is also required over here.

However in the case of buttons we knew which one had to increment counter (the right button) and which one had to decrement it (the left button).

The question is that how do we know exactly this thing in terms of a swipe i.e when to write counter++ and when to write counter--. Try to think on it - it's very easy!

Assuming you've had a go at it, let's see what we need to solve this problem.

Calculate the difference between endPos and startPos, and if it's negative it would mean that we have swiped left. The other condition i.e positive difference would then obviously constitute a swipe to the right.
A swipe to the left would mean pulling the next slide in and hence the expression counter++, whereas a swipe to the right would mean pulling the previous slide in and hence the expression counter--.

Check this yourself and see if what you do matches with this description. Frankly speaking you don't even need to set up some code to confirm this - your imagination is sufficient!

Now we are in good form to write a working touch slider, so let's finally do it.

var startTime = endTime = 0;
var benchmarkTime = 200;
var startPos = endPos = deltaPos = 0;
var halfSlideWidth = slides[0].getBoundingClientRect().width / 2;

slidesWrapper.addEventListener("touchstart", function(e) {
    startTime = new Date();
    startPos = e.changedTouches[0].clientX;
});

slidesWrapper.addEventListener("touchend", function(e) {
    endTime = new Date();
    endPos = e.changedTouches[0].clientX;
    deltaPos = endPos - startPos;
    if ((endTime - startTime) < benchmarkTime || deltaPos >= halfSlideWidth) {
        if (deltaPos === 0) return; // a mere touch,  no swipe
        else if (deltaPos < 0) counter++; // a left swipe
        else counter--; // a right swipe
        navigateSlider();
    } else {
        // unsuccessfull swipe
    }
});

Seeing the code above you'll firstly notice the fact that we've introduced a new variable deltaPos into the game. The reason for this is that since the expression endPos - startPos is to be utilised multiple times it is good to save it once in a variable and then reuse the variable itself at the required locations.

From lines 16 - 19 we are simply implementing what we discussed above about the swipe direction - nothing new!

Although this code works perfectly fine, there is still a tiny problem with it, specifically at the end slides. Can you spot the problem out?

Wrong Touch Slider

Ideally when we reach the end slides i.e first or the last one, we shall not be able to navigate backwards from the first slide and forwards from the last slide. However with this code there is simply no prevention to this - we can still slide back from slide 1 and ahead of slide 3.

There needs to be some conditionals where we can check for the swipe direction and the slide number and compare both of them to prevent left navigation on the first slide and right navigation on the last one.

Hence following is the complete code for the touchend listener.

slidesWrapper.addEventListener("touchend", function(e) {
    endTime = new Date();
    endPos = e.changedTouches[0].clientX;
    deltaPos = endPos - startPos;
    if ((endTime - startTime) < benchmarkTime || deltaPos >= halfSlideWidth) {
        if (deltaPos === 0) return; // a mere touch,  no swipe
        else if (deltaPos < 0 && counter !== (len - 1)) counter++; // a left swipe
        else if (deltaPos > 0 && counter !== 0) counter--; // a right swipe
        navigateSlider();
    }
});
Here in line 8 we have changed the previous else statement to an else if so that we can check for the condition counter !== 0 as well.
In line 10, previously we had an else statement which we have removed here because it was serving no purpose in real.

This rectifies out the navigation issue in our code and now we have a perfectly working touch interactive slider.

Rectified Touch Slider