Когда я начинал изучать JavaScript, мне очень хотелось понять как работают и делаются слайдеры, которые можно перелистывать свайпами или мышью, но материалов с хорошим объяснением именно того, что мне надо, я не нашел. Через какое-то время мне удалось сделать нечто подобное. И теперь я хочу написать об этом статью, чтобы другим людям, которые хотят понять как это все работает и сделать touch события для слайдера (и не только), было проще разобраться. Я постараюсь излагать по порядку и подкреплять объяснения наглядными примерами.
Я не считаю себя большим специалистом в JavaScript, всегда есть чему учиться, поэтому если знаете как написать какие-то фрагменты кода лучше/проще/эффективнее — обязательно напишите в комментарии.
Содержание:
- Какой функционал будем делать?
- Пишем HTML и CSS
- Как это будет работать?
- Пишем JavaScript
- Итого
- Полная версия кода
Можно сразу посмотреть пример.
1. Какой функционал будем делать?
Напишем с 0 простенький слайдер, который будет иметь следующие функции:
- реализуем touch и drag перелистывание слайдов;
- переключение слайдов с помощью стрелок-переключателей;
- блокировка стрелок-переключателей < и > на первом и последнем слайде соответственно.
Звучит совсем просто и я постараюсь писать тоже просто и понятно.
Если кому-нибудь будет интересно, то мы можем добавлять потом какой-то функционал, вроде точек-переключателей, бесконечной прокрутки слайдов, вертикальной ориентации, переключение слайдов с помощью клавиатуры и т.д. В конце концов можно завернуть в компонент. Напишите об этом в комментарии. Если будет хотя бы несколько комментариев за какой-то функционал, то реализуем его.
2. Пишем HTML и CSS
Что должен представлять из себя слайдер с точки зрения HTML и CSS?
Видимая часть слайдера (1 слайд) будет размером 200х200 пикселей. Снизу расположим стрелки-переключатели слайдов.
В итоге у нас будет вот это:

Рассмотрим подробнее изнутри.
Несколько слайдов должны идти по порядку и располагаться горизонтально в одну линию. Для этого нужно поместить их в отдельный блок, класс которого будет .slider-track и зададим ему display: flex. Он должен вмещать в себя все слайды горизонтально, то есть быть достаточной ширины, в этом примере зададим для слайдов flex-shrink: 0, чтобы они не сжимались.

Далее желательно скрыть все слайды кроме текущего. Для этого придется обернуть наш track в еще один блок, назовем его .slider-list. Ширина и высота у него будет равна ширине и высоте одного слайда, также у него будет overflow: hidden. Таким образом виден будет только один слайд, а остальные скрыты.

Теперь нужно создать стрелки-переключатели. Для бо?льшего удобства мы положим их в отдельный блок .slider-arrows и расположим его снизу слайдера. Но за пределами .slider-list (overflow: hidden) их не будет видно, поэтому можно:
- установить для
.slider-listpadding-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;
/* Чтобы запретить скролл страницы, если мы начали двигать слайдер по оси X */
touch-action: pan-y;
}
/* Если где-то внутри слайдера будут изображения,
то нужно задать им pointer-events: none,
чтобы они не перетаскивались мышью */
.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).

posThreshold= ширина слайда (200px) * 0,3 = 60. С этим числом мы будем сравниватьposFinal, например, еслиposFinal> 60, то переключаем слайд, иначе возвращаем в начальное положение. Само условие:
if (posFinal >= posThreshold) nextSlide() или prevSlide(); else currentSlide();
Если со вторым условием
elseболее-менее понятно, то с первым нужно разобраться подробнее.
Допустим мы превысили порог
posThresholdи будем переключать слайд, но теперь нужно понять в какую сторону мы двинули слайд, чтобы вызватьprev()илиnext()действие. Для этого мы будем сравниватьposInitиposX1. Пусть ширина нашего экрана 320px, чи?сла ширины идут слева направо, то есть 0px-320px (в самой левой точке экрана 0, в самой правой точке экрана 320). Представим, что слайдер расположен по центру, прикладываем палец в самый центр и получаемposInit160. Теперь, если мы будем вести палец влево (допустим до края экрана), тоposX1будет уменьшаться с 160 до 0. Если будем вести палец вправо до края экрана, тоposX1будет увеличиваться c 160 до 320. Итак, мы приложили палец в центр и провели немного влево, при окончании свайпаposInit160, аposX1100, значит мы прошли порог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 недоступной на первом слайде
// и доступной в остальных случаях
// делаем стрелку next недоступной на последнем слайде
// и доступной в остальных случаях
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;
// p.s. event - аргумент по умолчанию в функции
},
// или es6
getEvent = () => event.type.search('touch') !== -1 ? event.touches[0] : event,
swipeStart = function() {
let evt = getEvent();
// берем начальную позицию курсора по оси Х
posInit = posX1 = evt.clientX;
// убираем плавный переход, чтобы track двигался за курсором без задержки
// т.к. он будет включается в функции slide()
sliderTrack.style.transition = '';
// и сразу начинаем отслеживать другие события на документе
document.addEventListener('touchmove', swipeAction);
document.addEventListener('touchend', swipeEnd);
document.addEventListener('mousemove', swipeAction);
document.addEventListener('mouseup', swipeEnd);
},
swipeAction = function() {
let evt = getEvent(),
// для более красивой записи возьмем в переменную текущее свойство transform
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)`;
// можно было бы использовать метод строк .replace():
// sliderTrack.style.transform = style.replace(trfRegExp, match => match - posX2);
// но в дальнейшем нам нужна будет текущая трансформация в переменной
} ...Пояснение к функции 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 раз.
- Слайдер можно "схватить", когда слайд еще не закончил перемещение.
Для исправления этого поведения нужно объявить переменную allowSwipe и регулировать ей запрет свайпа.
Описывать это подробно я уже не буду. Просто выложу этот код ниже.
И в примере все эти условия уже будут сделаны. Также для наглядности меняется курсор на слайдере.
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();
});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,
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();
});

andreymal
Подход с posThreshold будет плохо работать на больших слайдерах во всю ширину экрана — пользователю придётся делать длинные размашистые движения мышкой/пальцем, чтобы переключить этот чёртов слайд. Лучше вместо координат считать скорость курсора и переключать слайд в зависимости от вектора направления скорости — тогда будет достаточно сколь угодно малого движения
jcsb Автор
Это имеет смысл, спасибо)