Решил написать вторую часть про создание слайдера без библиотек. Для того, чтобы понимать, что здесь происходит нужно просмотреть первую часть. А еще лучше просмотреть код. Если в первая статья была для новичков, то для чтения этой части нужен более продвинутый уровень. Будет много кода. Мы будем менять и переписывать это. Также как и в прошлый раз, наливаем себе чаек☕ и начинаем. Ссылка на новую рабочую версию в конце статьи. Советую читать код по порядку. Я его разделил на этапы.
Пререквизиты
- Понимание что такое ООП(геттеры, сеттеры, приватные свойства).
- Понимание, что такое события в JavaScript.
- Умение читать чужой код.
В предыдущей статье мы написали простой слайдер с переключением по кнопкам. Ссылка на код здесь. Но, обычно, слайдеры еще имеют прокрутку при касании или нажатии на него мышью.
Для касаний на телефоне у нас есть такие события.
- touchstart
- touchmove
- touchend
Для начала, давайте определимся с функционалом. Двигаем мы слайдер с помощью transform: translateX. Сам алгоритм работы такой:
- На событие touchstart отслеживаем точку, куда мы коснулись.
- На событие touchmove, в зависимости в какую сторону мы двигаем палец, прибавляем или отнимаем у переменной position значение. touchStartX(начальная точка, куда мы коснулись) - currentX(текущая позиция пальца или мышки). В виде псевдокода это так: position = position + (touchStartX - currentX).
- На событие touchEnd смотрим: если position > sliderElemWidth / 7, тогда докручиваем до конца. Если меньше возвращаем переменной position значение, с которого мы начинали крутить.
В коде алгоритм будет понятнее. Почему мы отслеживаем прокрутку не до половины экрана, а делим на 7 частей?
На телефоне мы привыкли прокручивать одним быстрым касанием. Почти всегда, прокрутка не доходит до середины, но мы ожидаем пролистывания. Поэтому, чтобы слайдер пролистывался при быстром касании - делим на 7 частей.
C архитектурной точки зрения, слайдер - это отдельная сущность, которая может быть на разных страницах. Поэтому прежде, чем начнем писать логику, отойдем от безобразного стиля к ООП.
Переименуем index.js в slider.js. Создадим приватные свойства, опишем конструктор, добавим обработку событий и напишем название методов.
Сверху мы инициализировали начальный функционал для слайдера. В constructor добавили геттеры, сеттеры и обработчики событий. Пройдемся по важному.
- debounce - это функция, которая выполняет переданную ей функцию, не чаще чем в указанное кол-во секунд. Обычно, используется при scroll событиях и поисках с запросом на сервер. В нашем случае, если пользователь нажимает на стрелки быстрее, чем проходит анимация пролистывания(300ms), то смена активного индекса отработает быстрее анимации. И визуально, активный элемент будет не соответствовать выбранному слайду. Поэтому мы оборачиваем функции в this.onRightBtnClick и this.onLeftBtnClick в debounce c задержкой в 300ms
- Мы добавили обработчик события window.resize. Это нужно для корректного отображения слайдера, если мы перейдем в портретную версию экрана. Из-за того, что на портретной версии ширина изменилась, а при открытии странице она была меньше, мы увидим баг с пролистыванием. Нам нужно будет обновить свойство sliderElemWidth и поставить его снова в transform: translateX.
- startingPosition - это начальная позиция transform: translateX, с которой мы начнем листать. Она будет нужна для момента, когда пользователь не дотянул слайд до 1/7 экрана. Тогда у нас нет пролистывания и мы возвращаем слайд в начальное состояние.
Теперь, давайте восстановим уже работающую логику и в this.onWindowResize добавим логику обновления для новой ширины.
Когда переходим в портретный режим, ставим позицию(this.currentPosition) равной новой ширине экрана умноженной на активный индекс. Меняем translateX на этот this.currentPosition. Смотрим, что все работает
Вот отрефакторенная версия.
Теперь перейдем к прокрутке по касаниям. В метод touchStart напишем такой код:
Здесь все просто.
- Ставим анимацию в 100ms, потому что если мы будем двигать слайдер на 300ms, будет медленно и будет казаться, что лагает.
- Запоминаем координаты точки, куда мы коснулись.
Теперь напишем метод onTouchMove
Сначала мы смотрим координату, где находится палец сейчас. Это currentX. Чтобы понимать в какую сторону нужно двигать слайдер, мы сравниваем текущую координату(currentX) с предыдущей(this.touchStartX). Если текущая меньше, тогда мы сохраняем значение: на сколько px был сдвиг(offsetX) и двигаем слайдер влево. Если больше, то двигаем вправо. Убедимся, что все работает.
Все отлично. Осталось сделать автоматическое пролистывание, когда мы убираем палец с экрана.
Для этого в метод onRelease добавим такой код.
Тут мы возвращаем анимацию в 300ms. И на 4 строчке, в переменной moveTo, высчитываем расстояние, на которое нам надо подвинуть слайдер, чтобы показалась новая картинка. Далее, смотрим, куда мы листали в последний раз: right или left. И сравниваем: сдвинулся ли слайдер больше, чем на одну седьмую экрана (6 строчка и 19 строчка). Если сдвинулся, двигаем его на расстояние в moveTo (7 и 20 строчки), а если нет, то ставим слайдер на начальную позицию(16 и 30 строчки).
Снова проверим, что все в порядке.
Все работает. Осталось сделать тоже самое для прокрутки с захватом мыши. Для этого используем события:
- onmousedown
- onmousemove
- onmouseup
Все работает по такой же логике, но onmousemove работает всегда, когда мы водим мышкой, поэтому надо в onmousedown и onmouseup следить, когда мы зажали или отжали кнопку. В этом нам поможет свойство isCaptured. Чтобы понять, вернемся к коду.
Метод onMouseDown:
Все тоже самое, что и в onTouchStart, только добавили переменную isCaptured.
Метод onMouseMove:
Если захватили мышью, тогда двигаем. Тут появился новый метод onMove. Это точно такой же метод touchMove, просто всю логику вычислений, кроме получения currentX, мы вынесли в onMove.
Когда мы отпускаем мышь и убираем палец метод у нас один - onRelease. Только еще добавим туда this.captured = false. Это нужно для понимания, что мы отпустили слайдер. Вот финальная версия кода.
Сегодня мы написали полностью работающий слайдер. Основная цель была - показать идею, как работают свайпы, на базовом уровне. Для картинок можно еще добавить lazyload.
Полезные ссылки:
Рабочий код - https://github.com/exoriri/slider-article/tree/feature/drag-and-touch
Рабочая версия - https://exoriri.github.io/slider-article/
Первая часть статьи -https://zen.yandex.ru/media/id/5f1d820734c8dd636d375e3a/pishem-slaider-bez-bibliotek-javascript-chast-1-63069d5c4ca82f0083ea773a
Спасибо, если продержались и дочитали до конца. Буду рад прочитать, что вы думаете, в комментариях:)
#программирование #javascript #слайдер #it