July 21st, 2021

JavaScript: карусель картинок, функции для кнопок

Начало:
1. JavaScript: карусель картинок, строим каркас 1
2. JavaScript: карусель картинок, строим каркас 2

Заканчиваю разбор задачи «Карусель» к подразделу 2.1 «Введение в браузерные события» второй части учебника по JavaScript.

Итак, у нас есть каркас карусели, созданный на заданной HTML-странице с помощью кода на языках HTML и CSS в предыдущих двух постах. В скрипте на языке JavaScript выполнены некоторые предварительные настройки, которые в принципе можно было задать в стилях CSS, но мне захотелось сделать так, чтобы размер картинок задавался только в одном месте. Этот размер задан в стиле HTML-элемента img, а оттуда наш скрипт его считывает, и определяет по нему некоторые из размеров каркаса карусели.

Теперь нам осталось написать функционал для имеющихся на каркасе карусели кнопок «влево» и «вправо», которые заставят карусель из картинок двигаться.

Двигать мы будем HTML-список ul, в пунктах которого содержатся десять тестовых картинок. Чтобы дать HTML-списку способность передвигаться, изменим в его стиле тип позиционирования со статического (по умолчанию) на относительное с помощью указания position: relative;:
ul {
  margin: 0;             /* уберем внешние и внутренние умолчательные */
  padding: 0;            /* отступы, назначенные браузером */
  font-size: 0px;        /* уберем промежутки между картинками */
                         /* и промежутки для «свисающих» букв */
  width: 9999px;         /* разместим пункты списка в одну строку */
  position: relative;    /* даем списку способность двигаться */
}

ul img {
  width: 130px;          /* размеры картинок */
  height: 130px;
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
                         /* и уберем маркеры списка */
}

Теперь мы можем двигать HTML-список влево и вправо, изменяя из своего скрипта его CSS-свойство left (координата левого верхнего угла HTML-элемента ul).

Подробнее про CSS-позиционирование можно почитать тут (эту ссылку я уже давал неоднократно):
https://developer.mozilla.org/ru/docs/Learn/CSS/CSS_layout/Positioning

Сначала я написал две функции, по одной на каждую кнопку карусели («влево», «вправо»). А потом увидел, что эти функции похожи друг на друга за исключением двух моментов:

1) При нажатии на кнопку «влево» HTML-список движется вправо, то есть значение его CSS-свойства left увеличивается. А при нажатии на кнопку «вправо» HTML-список движется влево, то есть значение его CSS-свойства left уменьшается.

(Тут может появиться вопрос: почему при нажатии кнопок, указывающих в одном направлении, HTML-список движется в противоположном направлении? Ответ: тут мы действуем по тому же принципу, по которому в браузере работает вертикальная полоса прокрутки: если мы перемещаем полосу прокрутки вверх, то отображаемая HTML-страница движется вниз; если же мы перемещаем полосу прокрутки вниз, то отображаемая HTML-страница движется вверх. По тому же принципу работает и горизонтальная полоса прокрутки: направление движения полосы прокрутки противоположно движению отображаемой HTML-страницы.)

2) При движении нашего HTML-списка слева и справа существуют ограничители, далее которых наш HTML-список двигаться не должен, иначе картинки в карусели «уедут» за левую или правую границу области просмотра карусели. Ограничитель при движении влево отличается от ограничителя при движении вправо. В качестве ограничителя будем использовать определенное значение CSS-свойства left HTML-списка.

Так как две функции отличались только в этих двух моментах, я решил объединить эти две функции в одну, чтобы не дублировать код. Дополняем код скрипта из предыдущих двух постов следующим кодом:
// количество картинок в шаге карусели
const STEP = 3;
// количество картинок в карусели всего
const NUM_PIC = document.querySelectorAll("ul img").length;

arrowLeft.onclick = () => move(1);  // кнопка «влево»
arrowRight.onclick = () => move(0); // кнопка «вправо»

function move(direction) {
    let ul = document.querySelector("ul");
    let x = ul.style.left;        // получим координату списка
    if (x == "") { x = 0; }
    else { x = x.slice(0, -2); }  // удалим символы "px"
    
    if (direction) {
        x = +x + PIC_SIZE * STEP; // двигаем список вправо (кнопка «влево»)
        if (x > 0) x = 0;         // ограничитель справа
    } else {
        x = +x - PIC_SIZE * STEP; // двигаем список влево (кнопка «вправо»)
        let min = -(NUM_PIC * PIC_SIZE - VIEWPORT_SIZE);
        if (x < min) x = min;     // ограничитель слева
    }
    
    ul.style.left = x + "px";     // запишем вычисленную координату
}

Константы PIC_SIZE и VIEWPORT_SIZE были определены в предыдущих двух постах. PIC_SIZE — это ширина (она же — высота) каждой из картинок в карусели, а VIEWPORT_SIZE — ширина области просмотра карусели (мы ее определили такой, чтобы в область просмотра смогли влезть три картинки, то есть 3 * PIC_SIZE).

С помощью константы STEP, кстати, можно поменять количество картинок, прокручиваемых в карусели за одно нажатие на кнопку «влево» или «вправо» (при работе скрипта фактическое значение шага прокрутки карусели может быть и меньше, если при очередном шаге карусель достигла левого или правого ограничителя). Я сделал эту константу равной числу 3, потому что так работает действующий пример карусели от авторов задачи. Но мне больше нравится, когда эта константа равна числу 1, тогда картинки в карусели «листаются» по одной.

Если по ограничителю справа всё понятно, это число 0, то по ограничителю слева могут понадобиться пояснения. Если ограничитель слева будет равен ширине всех тестовых картинок NUM_PIC * PIC_SIZE, то все картинки «уедут» за левую границу области просмотра карусели. А хочется, чтобы в области просмотра карусели при этом оставались видны последние в списке картинки. Чтобы это реализовать, корректируем ограничитель слева, вычитая из ширины всех тестовых картинок ширину области просмотра карусели.

Задача решена.

CSS-анимация

Я уже разбирал анимацию в одном из предыдущих постов (тут). Чтобы анимировать движение HTML-списка в нашей карусели, сделаем то же, что и раньше: воспользуемся CSS-свойством transition (для анимации самого первого движения HTML-списка добавим в стиль HTML-списка еще и первоначальное значение CSS-свойства left). Меняем код стиля HTML-списка (изменения я выделил красным):
ul {
  margin: 0;             /* уберем внешние и внутренние умолчательные */
  padding: 0;            /* отступы, назначенные браузером */
  font-size: 0px;        /* уберем промежутки между картинками */
                         /* и промежутки для «свисающих» букв */
  width: 9999px;         /* разместим пункты списка в одну строку */
  position: relative;    /* даем списку способность двигаться */
  transition: all 1s;    /* анимация движения картинок */
  left: 0;               /* для анимации самого первого движения картинок */
}

ul img {
  width: 130px;          /* размеры картинок */
  height: 130px;
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
                         /* и уберем маркеры списка */
}

Дополнительно

В коде заданной HTML-страницы авторы задачи дают ученику небольшой скрипт на языке JavaScript, не имеющий отношения к финальному решению, которое напишет ученик. Этот скрипт при желании можно использовать при написании решения задачи, он может играть вспомогательную роль при тестировании решения. Вот этот скрипт:
// отметить картинки для удобства разработки
// этот код может быть удалён по вашему усмотрению
let i = 1;
for(let li of document.querySelectorAll("li")) {
    li.style.position = "relative";
    li.insertAdjacentHTML("beforeend", `<span style="position:absolute; left:0; top:0;">${i}</span>`);
    i++;
}

Этот скрипт должен показывать в левом верхнем углу каждой картинки в карусели порядковый номер этой картинки. Такая нумерация, по идее, должна помочь не запутаться в картинках при прокрутке карусели влево-вправо. На этапе разработки вычисления координат HTML-списка при его движении нумерация картинок действительно может быть полезна.

Однако, авторы задачи забыли, что при разработке каркаса карусели (см. два предыдущих поста) в стиле HTML-списка мы внесли указание font-size: 0px;, делающее любой текст в списке невидимым. Поэтому вышеуказанный скрипт в показанной редакции не будет работать так, как было задумано.

Я добавил в определение стиля HTML-элемента span в обсуждаемом скрипте указание font-size: 16px; и скрипт заработал. Работающая редакция скрипта (красным я выделил изменение):
// отметить картинки для удобства разработки
// этот код может быть удалён по вашему усмотрению
let i = 1;
for(let li of document.querySelectorAll("li")) {
    li.style.position = "relative";
    li.insertAdjacentHTML("beforeend", `<span style="position:absolute; font-size: 16px; left:0; top:0;">${i}</span>`);
    i++;
}

Еще правильнее, наверное, будет указание font-size: initial;, то есть размер шрифта, указанный в настройках браузера по умолчанию.