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.
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!
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
andtouchend
- 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?
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.
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.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?
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();
}
});
else
statement to an else if
so that we can check for the condition counter !== 0
as well.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.