When I started learning JavaScript, I really wanted to understand how sliders work and are made that can be flipped with swipe or mouse, but I did not find materials with a good explanation of exactly what I need. After some time, I managed to do something similar. And now I want to write an article about this, so that other people who want to understand how it all works and make touch events for the slider (and not only) make it easier to figure it out. I will try to set out in order and support the explanations with illustrative examples.
I do not consider myself a great specialist in JavaScript, there is always something to learn, so if you know how to write some code fragments better / easier / more efficiently, be sure to write in the comments.
Content:
- What functionality will we do?
- Writing HTML and CSS
- How will it work?
- Writing javascript
- Total
- Full version code
You can immediately see an example .
1. What functionality will we do?
Let's write with 0 a simple slider that will have the following functions:
- we implement touch and drag turning over slides;
- switch slides using the arrow keys;
- lock the arrow keys โ and โ on the first and last slide, respectively.
.
- , - , -, , , .. . . - , .
2. HTML CSS
HTML CSS?
(1 ) 200200 . - .
:

.
. , .slider-track
display: flex
. , , flex-shrink: 0
, .

. track , .slider-list
. , overflow: hidden
. , .

-. ฬ .slider-arrows
. .slider-list
(overflow: hidden
) , :
.slider-list
padding-bottom ;.slider-list
, .slider
( ) .
ฬ CSS.
HTML :
<div class="slider">
<div class="slider-list">
<div class="slider-track">
<div class="slide">1</div>
<div class="slide">2</div>
<div class="slide">3</div>
<div class="slide">4</div>
<div class="slide">5</div>
</div>
</div>
<div class="slider-arrows">
<button type="button" class="prev">←</button>
<button type="button" class="next">→</button>
</div>
</div>
←
→
HTML-, , .
CSS :
.slider {
position: relative;
width: 200px;
height: 200px;
margin: 50px auto 0;
user-select: none;
touch-action: pan-y;
}
.slider img {
poiner-events: none;
}
.slider-list {
width: 200px;
height: 200px;
overflow: hidden;
}
.slider-list.grab {
cursor: grab;
}
.slider-list.grabbing{
cursor: grabbing;
}
.slider-track {
display: flex;
}
.slide {
width: 200px;
height: 200px;
flex-shrink: 0;
font-size: 50px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #000;
}
.slider-arrows {
margin-top: 15px;
text-align: center;
}
.next,
.prev {
background: none;
border: none;
margin: 0 10px;
font-size: 30px;
cursor: pointer;
}
.next.disabled,
.prev.disabled {
opacity: .25;
pointer-events: none;
}
:

overflow: hidden
:

, .
3. ?
transform: translate3d(x, y, z)
, . transform: translateX(x)
will-change: transform
. style
match()
.
slideIndex
. , (200px). โ transform: translate3d(x, y, z)
x
: *
. -.
, .
. touchstart
( mousedown
), โ touchmove
(mousemove
), โ touchend
(mouseup
).
event.clientX
( ) :
3 โ swipeStart
, swipeAction
swipeEnd
.
() X ( posX1
posInit
, posX1
, posInit
).
, posX1
, posX2
, style.transform
, . posX1
.
:
posX1
posInit
โ , ( event.clientX
). swipeStart
, , 320px, posInit
posX1
160. swipeAction
posX1
.

posX2
โ posX1
event.clientX
. swipeAction
, , - , " " = 161, 160 โ 161 = -1, -1px. :

, , X, posX2
, . 0.5 10, ( , ฬ , , ).
, (posFinal
) "" (posThreshold
), .
. . , :
posFinal
= posInit
โ posX1
( , swipeAction
, , 200px, , posFinal
100).

else
- , .
posThreshold
, , prev()
next()
. posInit
posX1
. 320px, ฬ , 0px-320px ( 0, 320). , , posInit
160. , ( ), posX1
160 0. , posX1
c 160 320. , , posInit
160, posX1
100, posThreshold
. :
if (posInit > posX1) nextSlide(); else if (posInit < posX1) prevSlide();
, , click
. posInit
=== posX1
. , .
. JavaScript.
4. JavaScript
, , "" , , . , . , .
:
.slider-track
transfrom: translate3d(Xpx, 0px, 0px)
;transfrom: translate3d(0px, 0px, 0px)
style
, ;style.transform
match()
;swipeStart
.slider-list
touchstart
mousedown
;swipeStart
document
swipeAction
swipeEnd
touchmove
(mousemove
) touchend
(mouseup
) , ;posThreshold
, slideIndex
;posThreshold
, ;- -, .
.
. HTML- querySelector DOM, querySelector. , . .
let slider = document.querySelector('.slider'),
sliderList = slider.querySelector('.slider-list'),
sliderTrack = slider.querySelector('.slider-track'),
slides = slider.querySelectorAll('.slide'),
arrows = slider.querySelector('.slider-arrows'),
prev = arrows.children[0],
next = arrows.children[1],
slideWidth = slides[0].offsetWidth,
slideIndex = 0,
posInit = 0,
posX1 = 0,
posX2 = 0,
posFinal = 0,
posThreshold = slideWidth * .35,
trfRegExp = /[-0-9.]+(?=px)/,
slide = function() {
sliderTrack.style.transition = 'transform .5s';
sliderTrack.style.transform = `translate3d(-${slideIndex * slideWidth}px, 0px, 0px)`;
prev.classList.toggle('disabled', slideIndex === 0);
next.classList.toggle('disabled', slideIndex === --slides.length);
} ...
trfRegExp
โ , transform
.slider-track
.
event.clientX
, touches โ event.touches[0].clientX
. event
.. , getEvent
. event.type
touch touch event. .
getEvent = function() {
return event.type.search('touch') !== -1 ? event.touches[0] : event;
},
getEvent = () => event.type.search('touch') !== -1 ? event.touches[0] : event,
swipeStart = function() {
let evt = getEvent();
posInit = posX1 = evt.clientX;
sliderTrack.style.transition = '';
document.addEventListener('touchmove', swipeAction);
document.addEventListener('touchend', swipeEnd);
document.addEventListener('mousemove', swipeAction);
document.addEventListener('mouseup', swipeEnd);
},
swipeAction = function() {
let evt = getEvent(),
style = sliderTrack.style.transform,
transform = +style.match(trfRegExp)[0];
posX2 = posX1 - evt.clientX;
posX1 = evt.clientX;
sliderTrack.style.transform = `translate3d(${transform - posX2}px, 0px, 0px)`;
} ...
swipeAction
:
transform
. :
translate3d(0px, 0px, 0px)
"px
". transform
. .
: [-0-9.]+(?=px)
, :
[-0-9.]
โ , "" " 0 9" "";+
โ , 1 , , : 5, 101.10, -19, -12.5 ..;(?=px)
โ , , "px".
.slider-track
, sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)'
, swipeAction
. :

( ) โ swipeAction
, .slider-track
. swipeEnd
, , :
swipeEnd = function() {
posFinal = posInit - posX1;
document.removeEventListener('touchmove', swipeAction);
document.removeEventListener('mousemove', swipeAction);
document.removeEventListener('touchend', swipeEnd);
document.removeEventListener('mouseup', swipeEnd);
if (Math.abs(posFinal) > posThreshold) {
if (posInit < posX1) {
slideIndex--;
} else if (posInit > posX1) {
slideIndex++;
}
}
if (posInit !== posX1) {
slide();
}
};
touch drag , . : posFinal
posThreshold
, , , . Math.abs()
.

. , .
arrows.addEventListener('click', function() {
let target = event.target;
if (target === next) {
slideIndex++;
} else if (target === prev) {
slideIndex--;
} else {
return;
}
slide();
});
sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)';
slider.addEventListener('touchstart', swipeStart);
slider.addEventListener('mousedown', swipeStart);
. .
. tocuh , drag -. , , :

Y, , ( ).

, , .

, . swipeAction
, swipeStart
1 .
- The slider can be "grabbed" when the slide has not yet finished moving.
To fix this behavior, you need to declare a variable allowSwipe
and adjust the swipe ban on it.
I will not describe it in detail. Just lay out this code below .
And in the example, all these conditions will already be made. Also, for clarity, the cursor on the slider changes.
Full code (swipe, drag, arrows)let slider = document.querySelector('.slider'),
sliderList = slider.querySelector('.slider-list'),
sliderTrack = slider.querySelector('.slider-track'),
slides = slider.querySelectorAll('.slide'),
arrows = slider.querySelector('.slider-arrows'),
prev = arrows.children[0],
next = arrows.children[1],
slideWidth = slides[0].offsetWidth,
slideIndex = 0,
posInit = 0,
posX1 = 0,
posX2 = 0,
posFinal = 0,
posThreshold = slides[0].offsetWidth * 0.35,
trfRegExp = /([-0-9.]+(?=px))/,
getEvent = function() {
return (event.type.search('touch') !== -1) ? event.touches[0] : event;
},
slide = function() {
if (transition) {
sliderTrack.style.transition = 'transform .5s';
}
sliderTrack.style.transform = `translate3d(-${slideIndex * slideWidth}px, 0px, 0px)`;
prev.classList.toggle('disabled', slideIndex === 0);
next.classList.toggle('disabled', slideIndex === --slides.length);
},
swipeStart = function() {
let evt = getEvent();
posInit = posX1 = evt.clientX;
sliderTrack.style.transition = '';
document.addEventListener('touchmove', swipeAction);
document.addEventListener('mousemove', swipeAction);
document.addEventListener('touchend', swipeEnd);
document.addEventListener('mouseup', swipeEnd);
},
swipeAction = function() {
let evt = getEvent(),
style = sliderTrack.style.transform,
transform = +style.match(trfRegExp)[0];
posX2 = posX1 - evt.clientX;
posX1 = evt.clientX;
sliderTrack.style.transform = `translate3d(${transform - posX2}px, 0px, 0px)`;
},
swipeEnd = function() {
posFinal = posInit - posX1;
document.removeEventListener('touchmove', swipeAction);
document.removeEventListener('mousemove', swipeAction);
document.removeEventListener('touchend', swipeEnd);
document.removeEventListener('mouseup', swipeEnd);
if (Math.abs(posFinal) > posThreshold) {
if (posInit < posX1) {
slideIndex--;
} else if (posInit > posX1) {
slideIndex++;
}
}
if (posInit !== posX1) {
slide();
}
};
sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)';
slider.addEventListener('touchstart', swipeStart);
slider.addEventListener('mousedown', swipeStart);
arrows.addEventListener('click', function() {
let target = event.target;
if (target === next) {
slideIndex++;
} else if (target === prev) {
slideIndex--;
} else {
return;
}
slide();
});
The most complete codelet slider = document.querySelector('.slider'),
sliderList = slider.querySelector('.slider-list'),
sliderTrack = slider.querySelector('.slider-track'),
slides = slider.querySelectorAll('.slide'),
arrows = slider.querySelector('.slider-arrows'),
prev = arrows.children[0],
next = arrows.children[1],
slideWidth = slides[0].offsetWidth,
slideIndex = 0,
posInit = 0,
posX1 = 0,
posX2 = 0,
posY1 = 0,
posY2 = 0,
posFinal = 0,
isSwipe = false,
isScroll = false,
allowSwipe = true,
transition = true,
nextTrf = 0,
prevTrf = 0,
lastTrf = --slides.length * slideWidth,
posThreshold = slides[0].offsetWidth * 0.35,
trfRegExp = /([-0-9.]+(?=px))/,
getEvent = function() {
return (event.type.search('touch') !== -1) ? event.touches[0] : event;
},
slide = function() {
if (transition) {
sliderTrack.style.transition = 'transform .5s';
}
sliderTrack.style.transform = `translate3d(-${slideIndex * slideWidth}px, 0px, 0px)`;
prev.classList.toggle('disabled', slideIndex === 0);
next.classList.toggle('disabled', slideIndex === --slides.length);
},
swipeStart = function() {
let evt = getEvent();
if (allowSwipe) {
transition = true;
nextTrf = (slideIndex + 1) * -slideWidth;
prevTrf = (slideIndex - 1) * -slideWidth;
posInit = posX1 = evt.clientX;
posY1 = evt.clientY;
sliderTrack.style.transition = '';
document.addEventListener('touchmove', swipeAction);
document.addEventListener('mousemove', swipeAction);
document.addEventListener('touchend', swipeEnd);
document.addEventListener('mouseup', swipeEnd);
sliderList.classList.remove('grab');
sliderList.classList.add('grabbing');
}
},
swipeAction = function() {
let evt = getEvent(),
style = sliderTrack.style.transform,
transform = +style.match(trfRegExp)[0];
posX2 = posX1 - evt.clientX;
posX1 = evt.clientX;
posY2 = posY1 - evt.clientY;
posY1 = evt.clientY;
if (!isSwipe && !isScroll) {
let posY = Math.abs(posY2);
if (posY > 7 || posX2 === 0) {
isScroll = true;
allowSwipe = false;
} else if (posY < 7) {
isSwipe = true;
}
}
if (isSwipe) {
if (slideIndex === 0) {
if (posInit < posX1) {
setTransform(transform, 0);
return;
} else {
allowSwipe = true;
}
}
if (slideIndex === --slides.length) {
if (posInit > posX1) {
setTransform(transform, lastTrf);
return;
} else {
allowSwipe = true;
}
}
if (posInit > posX1 && transform < nextTrf || posInit < posX1 && transform > prevTrf) {
reachEdge();
return;
}
sliderTrack.style.transform = `translate3d(${transform - posX2}px, 0px, 0px)`;
}
},
swipeEnd = function() {
posFinal = posInit - posX1;
isScroll = false;
isSwipe = false;
document.removeEventListener('touchmove', swipeAction);
document.removeEventListener('mousemove', swipeAction);
document.removeEventListener('touchend', swipeEnd);
document.removeEventListener('mouseup', swipeEnd);
sliderList.classList.add('grab');
sliderList.classList.remove('grabbing');
if (allowSwipe) {
if (Math.abs(posFinal) > posThreshold) {
if (posInit < posX1) {
slideIndex--;
} else if (posInit > posX1) {
slideIndex++;
}
}
if (posInit !== posX1) {
allowSwipe = false;
slide();
} else {
allowSwipe = true;
}
} else {
allowSwipe = true;
}
},
setTransform = function(transform, comapreTransform) {
if (transform >= comapreTransform) {
if (transform > comapreTransform) {
sliderTrack.style.transform = `translate3d(${comapreTransform}px, 0px, 0px)`;
}
}
allowSwipe = false;
},
reachEdge = function() {
transition = false;
swipeEnd();
allowSwipe = true;
};
sliderTrack.style.transform = 'translate3d(0px, 0px, 0px)';
sliderList.classList.add('grab');
sliderTrack.addEventListener('transitionend', () => allowSwipe = true);
slider.addEventListener('touchstart', swipeStart);
slider.addEventListener('mousedown', swipeStart);
arrows.addEventListener('click', function() {
let target = event.target;
if (target.classList.contains('next')) {
slideIndex++;
} else if (target.classList.contains('prev')) {
slideIndex--;
} else {
return;
}
slide();
});
codepen.io