JS30. Задание 27 Click and Drag to Scroll

Демо: https://js3027.github.io/
Код: https://github.com/js3027/js3027.github.io

В результате должен получиться горизонтальный слайдер, который можно передвигать мышкой. Пример эффекта, там, где его увидел автор,  можно посмотреть здесь: http://hoverstat.es/features/maxime-guyon, а здесь он же изменённый в соответствии с инструкциями данного задания: http://murphscode.getforge.io/ вкладка Projects

Код слайдера предусматривает наличие нескольких слушателей событий и получение позиции слайдов на странице.

Разметка

HTML-код включает в себя контейнер div и несколько div внутри, которые представляют слайды.

<div class="items">
    <div class="item item1">01</div>
    <div class="item item2">02</div>
    <div class="item item3">03</div>
    ...
    <div class="item item25">25</div>
  </div>

Стили

В CSS есть несколько свойств, которые важны для этого проекта.
Во-первых, свойство overflow-x: scroll; которое предоставляет механизм горизонтальной прокрутки.

Затем у нас есть свойство white-space: nowrap, которое указывает, как обрабатывать пробелы внутри элемента. Свойство white-space устанавливает, как отображать пробелы между словами. В обычных условиях любое количество пробелов в коде HTML показывается на веб-странице как один. Значение nowrap означает, что пробелы не учитываются, переносы строк в коде HTML игнорируются, весь текст отображается одной строкой. Текст продолжается в одной строке до тех пор, пока не будет встречен тег <br>, который переносит текст на новую строку.

Ещё одним важным свойством является user-select: none. Оно определяет может ли пользователь выбирать и выделять текст. Значение none означает, что текст элемента и подэлементов выбрать и выделить невозможно.

Свойство display: inline-flex отличается от display: flex принципом взаимодействия с окружающими контейнер элементами.

flex — при рассчете ширины блоков приоритет у раскладки (при недостаточной ширине блоков контент может вылезать за границы);
inline-flex — приоритет у содержимого (контент растопыривает блоки до необходимой ширины, чтобы строчки, по возможности, поместились).


Последним свойством является item: nth-child, которое позволяет нам ориентировать каждый отдельный слайд вместе со всеми четными и нечетными слайдами. Когда мы выбираем четные слайды, мы устанавливаем преобразование rotateY(40deg). Затем мы делаем то же самое для нечетных слайдов, но с противоположным знаком rotateY(-40deg). Это, в дополнение к некоторой перспективе, дает нам эффект гармошки, наблюдаемый на картинке выше.

.item:nth-child(even) { transform: scaleX(1.31) rotateY(40deg); }
.item:nth-child(odd) { transform: scaleX(1.31) rotateY(-40deg); }


Код

Теперь, когда мы рассмотрели CSS, давайте займемся Javascript. Наша цель - определить начальный клик и движение мыши, как влево, так и вправо, расстояние, на которое мы переместили мышь. Затем мы применяем это расстояние для перемещения контейнера-слайдера.

Первое, что мы делаем, - находим контейнер слайдера:

var slider = document.querySelector(".items");

Затем создаём переменную-флаг, которая показывает перетаскиваем мы слайдер или нет

 var isDown = false;

Её свойство по умолчанию - false. Но как только мышка опустилась на слайдер, свойство меняется:

slider.addEventListener("mousedown", (e) => {
    isDown = true;
    ...
});


Поднялась, - меняется снова

slider.addEventListener("mouseleave", () => {
    isDown = false;
    ...
});


Наконец, в последней функции слушателя mousemove мы определяем значение флага isDown и выполняем функцию только если isDown = true;

slider.addEventListener("mousemove", (e) => {
    if (!isDown) return;  // stop the fn from running
    e.preventDefault();
    var x = e.pageX - slider.offsetLeft;
    var walk = (x - startX) * 3;
    slider.scrollLeft = scrollLeft - walk;
});


Ещё две переменные нужны, для хранения начального значения прокрутки и чтобы сохранять значение на которое мы прокрутили слайдер влево или вправо.

var startX;
var scrollLeft;



Затем нам нужно добавить довольно много слушателей. Мы будем отслеживать события mousedown
mouseleave
mouseup
mousemove


Для каждого из этих событий создаём собственную функцию

У первого слушателя будет событие mousedown, которое запускает анонимную функцию.
Код этой функции ниже:

slider.addEventListener("mousedown", (e) => {
    isDown = true;
    slider.classList.add("active");
    startX = e.pageX - slider.offsetLeft;
    scrollLeft = slider.scrollLeft;
});


Эта функция устанавливает переменную флага isDown в значение true.
Он также добавляет к контейнеру слайдера класс active, который меняет цвет фона на полупрозрачный белый, курсор на захват и масштаб на единицу.

.items.active {
    background: rgba(255, 255, 255, 0.3);
    cursor: grabbing;
    cursor: -webkit-grabbing;
    transform: scale(1);
}


Мы используем значение e.pageX, чтобы определить, где на странице мы нажали, и offsetLeft, чтобы отрегулировать значение позиции для любого поля или отступов.

Затем мы присваиваем переменной scrollLeft значение, на которое мы перемещали влево или вправо, используя метод scrollLeft. Мы будем использовать это значение scrollLeft в последнем прослушивателе событий.

scrollLeft = slider.scrollLeft;
slider.scrollLeft = scrollLeft - walk;

var walk = (x - startX) * 3;

Функции mouseleave и mouseup ничего не делают для контейнера слайдера. Они обе сохраняют переменную флага равной false и удаляют класс active.

slider.addEventListener("mouseleave", () => {
    isDown = false;
    slider.classList.remove("active");
});

slider.addEventListener("mouseup", () => {
    isDown = false;
    slider.classList.remove("active");
});


Мы хотим убедиться, что контент остается неизменным при срабатывании событий. Наша последняя функция запускается слушателем «mousemove» и передает аргумент события через анонимную функцию. Код этой функции:

slider.addEventListener("mousemove", (e) => {
    if (!isDown) return;  // stop the fn from running
    e.preventDefault();
    var x = e.pageX - slider.offsetLeft;
    var walk = (x - startX) * 3;
    slider.scrollLeft = scrollLeft - walk;
});


Мы хотим убедиться, что isDown = true, то есть курсор мыши находится над слайдером, прежде чем запускать эту функцию. Если это не так, мы прекращаем выполнение функции. Если true, тогда мы запускаем event.preventDefault, чтобы пользователь не мог выбрать какой-либо текст, или совершить любое другое непредвиденное действие.

Затем мы хотим вычислить нашу x-позицию. Переменная 'x' указывает нам, где находится курсор, когда мы перемещаемся влево или вправо. Это тот же код для переменной «startX», но мы должны пересчитывать позицию каждый раз, когда мы перемещаем мышь.

Если мы выведем в консоль эти два значения, увидим, что переменная «x» изменяется, а переменная «startX» остается постоянной. Переменная «walk» указывает, насколько далеко мы отклонились от этой начальной точки. Мы умножаем значение на три, чтобы ускорить эффект прокрутки. Наша последняя часть этой функции вычитает значение переменной «walk» из «scrollLeft», вычисленное в первой функции, для перемещения слайдов влево или вправо в контейнере. Это все для данного проекта

Код полностью:

var slider = document.querySelector(".items");
var isDown = false;
var startX;
var scrollLeft;

slider.addEventListener("mousedown", (e) => {
    isDown = true;
    slider.classList.add("active");
    startX = e.pageX - slider.offsetLeft;
    scrollLeft = slider.scrollLeft;
});

slider.addEventListener("mouseleave", () => {
    isDown = false;
    slider.classList.remove("active");
});

slider.addEventListener("mouseup", () => {
    isDown = false;
    slider.classList.remove("active");
});

slider.addEventListener("mousemove", (e) => {
    if (!isDown) return;  // stop the fn from running
    e.preventDefault();
    var x = e.pageX - slider.offsetLeft;
    var walk = (x - startX) * 3;
    slider.scrollLeft = scrollLeft - walk;
});