Introduction
Where we left our slider in the previous chapter is where we will now continue it from and give it an advanced level of touch interactivity.
The code examples from the previous chapter will more or less remain the same, with only certain differences at one or two places.
So why not move into actually discovering the journey to making our slider even more interactive than before!
Move with the swipe
The slider we constructed previously operated entirely on the touchstart
and touchend
events - only when we ended our swipe was when we saw any result from our touch interaction. Nothing else was there to indicate interactivity right away as we were performing a swipe across the screen.
The question is what can we possibly do to our slider, in particular give which event, so that it can signify interactivity right away on a swipe? Which event are we missing right now in the code?
An event to help
Well we simply need to let a touchmove
event come into the play.
Good to know which event to use, but now arises another question: how to use the touchmove
event? What do we need to do in this event's handler? Take a guess - what happens to your home page's thumbnails as you swipe across them.
In our case, ultimately, we need .slides-wrapper
to move as the touchmove
event occurs.
To accomplish this we simply need the event's object, the clientX
property and a style's addition.
Analyse the code below and comment whether it correctly works or not in moving slidesWrapper
with the swipe.
slidesWrapper.addEventListener("touchmove", function(e) {
slidesWrapper.style.transform = "translateX(" + e.changedTouches[0].clientX + "px)";
});
- Yes
- No
The code shown here is incorrect, with two problems.
Firstly we have used e.changedTouches[0].clientX
directly to translate .slides-wrapper
. The problem in doing so is answered by considering a simple scenario:
touchstart
at clientX = 100px
and then move 1px to the right. This results in clientX = 101px
. Now if we translate .slides-wrapper
by this value it will be translated by 101px, where it needed to move by 1px only.The turn over from this is that we need to move slidesWrapper
according to the difference between clientX
and startPos
(the initial clientX
) value.
Therefore what we can do to solve this is to include a new variable deltaPos
in the touchmove
handler which computes this difference, and then moves slidesWrapper
by this amount.
slidesWrapper.addEventListener("touchmove", function(e) {
deltaPos = e.changedTouches[0].clientX - startPos;
slidesWrapper.style.transform = "translateX(" + deltaPos + "px)";
});
The second problem in the code above is that we aren't saving the amount of translation already performed. This is explained as follows:
We swipe to go forward from slide 1 to slide 2, by
deltaPos = -450px
- and hence get the slider navigated (translateX
becomes -800px). Everything works fine until the point when we repeat this procedure on slide 2, to go to slide 3.Ideally we want
deltaPos
to be added to the previous translateX
for example going forward 100px should result in translateX = -800 + 100px
, but in our code we translate by deltaPos
directly.Due to this, no matter which ever slide we are on, once we swipe we come back to the slide 1 since
deltaPos ⩽ slideWidth
.This explanation might throw you out of track to be able to comprehend the second problem - so it is recommended that you read it carefully and try to fully understand each word.
Thereby the way to solve this issue is to simply save the amount of movement already performed. And to do this we use a variable moved
by assigning it a value in touchend
:
var moved = 0; // declare move somewhere at the top of the script
slidesWrapper.addEventListener("touchend", function(e) {
/* other code */
if ((endTime - startTime) < benchmarkTime || deltaPos >= halfSlideWidth) {
if (deltaPos === 0) return;
else if (deltaPos < 0 && counter !== (len - 1)) counter++;
else if (deltaPos > 0 && counter !== 0) counter--;
moved = halfSlideWidth * -2 * counter;
navigateSlider();
}
});
navigateSlider()
out of the conditional block (from lines 5-10) to line 11. This is done so that if the swipe is slow and before the halfway position, .slides-wrapper
can navigate back to its original position.With moved
calculated its only a matter of seconds to include it in touchmove
:
slidesWrapper.addEventListener("touchmove", function(e) {
deltaPos = e.changedTouches[0].clientX - startPos;
slidesWrapper.style.transform = "translateX(" + (deltaPos + moved) + "px)";
});
And here we have a working slider. Check it out and see what problems you still notice in it.
Janky experience
If you tried out the example above and have returned here with the thought "Uh, that slider was so janky" then you aren't alone in this experience. Anyone who will try the example will make this same remark.
But not everyone will try to think on the cause of this issue. Accordingly we'll first let you think for the reason..
Choose the best reason for the laggy behaviour of the slider example above.
.slides-wrapper
has atransition
property set on it which causes every translation to take some time to complete.- We do a resource-intensive calculation inside our
touchmove
handler. - We use
translateX
instead ofleft
to move our slider.
Well, it seems that the culprit for the sluggish sliding is the property transition
on .slides-wrapper
. As we get the element to translate on our swipe, every single pixel moved takes some time to do so. This time duration is the real cause of the jank.
transition
from .slides-wrapper
so that it can move freely.However this shall be the case only uptil the point we are swiping: at touchend
, when we end the swipe, we need to reinforce the transition back so that the slider can again navigate smoothly.
Hence the boil down is: remove the transition at touchstart
and reinforce it at touchend
.
slidesWrapper.addEventListener("touchstart", function(e) {
slidesWrapper.style.transition = "none"; // no transition
/* other code */
});
slidesWrapper.addEventListener("touchend", function(e) {
slidesWrapper.style.transition = "1s ease-in-out"; // add a transition
/* other code */
});
/* other code */
means the rest of the code in the event's handler.Now let's try out the example below and see if everything is working perfectly.
Well we notice some jankiness even after removing transition
from .slides-wrapper
. What could possibly have caused this?
Choose the best reason for the laggy behaviour of the slider example above, apart from transition
.
- The
touchmove
handler doesn't calle.preventDefault()
and hence doesn't remove the background work being done on every swipe. - We do a resource-intensive calculation inside our
touchmove
handler. translateX
is a janky property as it causes reflow of the page.
The culprit is the background work which gets performed once we start swiping. It causes our work in the touchmove
event janky as, intuitively, the event is already occupied with some tasks.
preventDefault()
will remove the default behaviour happening on the touchmove
event.The touchmove
handler likewise becomes:
slidesWrapper.addEventListener("touchmove", function(e) {
e.preventDefault();
deltaPos = e.changedTouches[0].clientX - startPos;
slidesWrapper.style.transform = "translateX(" + deltaPos + "px)";
});
Now let's finally try out the slider with these added rectifications and see if anymore are required. Well, in effect, there isn't any problem left!
And everything works amazingly!
Quick swiping
Go back to the example above and try swiping quickly across the slides. Were you able to notice some snappy navigations?
As you swipe across the slides very quickly there comes a point where they start to snap instead of smoothly sliding in. This is caused by the removal of transition
on touchstart
.
Here's the explanation of the issue:
touchstart
occurs, then touchmove
and finally touchend
where the swipe is detected to be fast; after which counter
is incremented, transition
is applied to .slides-wrapper
, and consequently the second slide is initiated to be brought in. It will take some time to completely slide-in due to the time-duration of transition
.Now if we start swiping again, before this duration; as always
touchstart
will occur and as a consequence remove transition
from .slides-wrapper
. This removal will cause the previous navigation (which was in the way to be completed) to snap in i.e be applied instantly without any transition.Therefore the turn out from this explanation is that we shouldn't remove transition
on touchstart
. But then where to remove it? Can you think of any other place?
After a bit of thinking into the solution, we arrive at the following conclusion:
transition
needs to be inforced only when a navigation is about to be performed i.e navigateSlider()
has to be invoked. This can be from a navigation or pagination button, a key event, a touchend event, or an autoplay.So why not take this transition add/removal logic into the navigateSlider()
function and remove it from all other places to get all cases dealt with rightaway.
No transition on .slides-wrapper
initially; therefore no transitioning on touchstart
and touchmove
. However on touchend
: 1) call navigateSlider()
2) apply transition
3) let the transition complete and then 4) remove it. Simple working!
Clever solution, isn't it ;)