Pull to refresh

Пишем генератор галереи изображений со встроенным слайдером

Reading time7 min
Views8.5K


Доброго времени суток, друзья!

Вместо введения (постановка задачи)


Все началось с изучения чужих слайдеров (готовых решений в сети, типа bxslider, owlcarousel и slick). Когда-нибудь я напишу подробные руководства по работе с этими инструментами (sweet dreams). Появилось желание написать свой слайдер. Однако вскоре (в том числе, после прочтения нескольких статей на Хабре) пришло осознание, что просто слайдер — это для слабаков. Нужно что-то более радикальное.

В итоге придумал себе такую задачу: написать генератор адаптивной галереи со встроенным слайдером.

Условия:

  • Возможность загружать любое количество изображений (из любого места на жестком диске).
  • Галерея состоит из загруженных изображений, разметка формируется «на лету» с соблюдением семантики HTML5.
  • Галерея одинаково хорошо смотрится на экранах с различным разрешением.
  • При клике на любом изображении генерируется слайдер.
  • При генерации слайдера затемняется фон.
  • Изображение, по которому кликнули — первый слайд.
  • Переключение слайдов реализовано через DOM.
  • Слайды переключаются плавно.
  • Возможность управлять переключением слайдов с помощью кнопок и клавиатуры.
  • Возможность вернуться к галерее при клике на текущем слайде и кнопке, а также с помощью клавиатуры.
  • Чистый JavaScript (вся разметка через JS).
  • Минимум кода.

Итак, поехали (как сказал Гагарин, отправляясь в космос).

Разметка выглядит так:
<div class="wrap">
    <input type="file" multiple accept="image/*">
    <button>generate gallery</button>
</div>

Из интересного здесь разве что атрибуты multiple и accept тега input. Первый атрибут позволяет загружать несколько файлов, второй — устанавливает фильтр на типы файлов, которые можно загрузить. В данном случае accept имеет значение «image/*», означающее, что можно загружать только изображения (любые).

Сразу наведем красоту (добавим стили):
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    min-height: 100vh;
    background: radial-gradient(circle, skyblue, steelblue);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

button {
    padding: 0.25em;
    font-family: monospace;
    text-transform: uppercase;
    cursor: pointer;
}

.darken {
    position: absolute;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.4);
    z-index: -1;
}

.slider {
    width: 100%;
    display: inherit;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
}

figure {
    margin: 0.5em;
    width: 300px;
    display: inherit;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    transition: 0.2s;
}

figcaption {
    font-size: 1.2em;
    text-transform: capitalize;
    text-align: center;
    margin-bottom: 0.25em;
    color: #ddd;
    text-shadow: 1px 1px rgba(0, 0, 0, 0.4);
}

img {
    max-width: 80%;
    max-height: 80vh;
    cursor: pointer;
}

.button {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 30px;
    background: none;
    border: none;
    outline: none;
    filter: invert();
}

.left {
    left: 2em;
}

.right {
    right: 2em;
}

.close {
    top: 2em;
    right: 1em;
}


Тут даже говорить не о чем (.darken — затемнение).

Двигаемся дальше… к JS.

Находим кнопку и вешаем на нее слушатель:
let button = document.querySelector("button");
button.addEventListener("click", generateGallery);

Весь дальнейший код будет находиться в функции generateGallery дабы избежать «not defined» без return:
function generateGallery() {
    // код галереи и слайдера
}

Находим input, проверяем, что он не пустой, получаем коллекцию загруженных файлов, удаляем .wrap и создаем контейнер для галереи:
let input = document.querySelector("input");
// проверяем, что input не пустой
if(input.files.length == 0) return;
let files = input.files;
// просто счетчик
let i;

// удаляем .wrap, он нам больше не нужен
let wrap = document.querySelector(".wrap");
document.body.removeChild(wrap);

// создаем контейнер для галереи, возможно, его следовало назвать gallery
let slider = document.createElement("div");
slider.className = "slider";
document.body.appendChild(slider);

Перебираем коллекцию файлов, получаем имя и адрес каждого файла, создаем разметку, формируем подписи к изображениям и сами изображения:
for (i = 0; i < files.length; i++) {
    let file = files[i];
    // URL.createObjectURL позволяет загружать файлы из любого места на жестком диске, но при этом возникают проблемы с получением готового кода
    // ссылки, сформированные этим методом, сохраняют работоспособность только во время сессии браузера
    // попытки декодировать строку с адресом при получении готового кода не привели к успеху, поэтому я решил воспользоваться другим способом
    /*let src = URL.createObjectURL(file);*/

    // получаем имя файла
    let name = file.name;

    // этот способ предполагает, что изображения находятся в папке img на одном уровне со скриптом
    let src = `img/${name}`;

    // создаем разметку: figure, figcaption, img
    let figure = document.createElement("figure");
    slider.appendChild(figure);

    let figcaption = document.createElement("figcaption");

    // для того, чтобы избавиться от расширения файла при выводе его имени в подпись к изображению, используем регулярное выражение
    // (?=\.) - опережающая проверка: найти один или более символов, за которыми следует точка
    // в данном случае мы не используем \w, потому что имя файла может быть не на латинице
    let regexp = /.+(?=\.)/;
    name = name.match(regexp);
    // получаем массив ["имя", index: 0, input: "имя.jpg", groups: undefined]
    // нас интересует первый элемент
    figcaption.innerText = name[0];
    figure.appendChild(figcaption);

    // создаем изображение
    let img = document.createElement("img");
    img.src = src;
    figure.appendChild(img);
}

Мы хотим генерировать слайдер при клике по изображению. Для этого, мы находим все figure и вешаем на каждый слушатель:
let figures = document.querySelectorAll("figure");
for (i = 0; i < figures.length; i++) {
    let figure = figures[i];
    figure.addEventListener("click", () => {
        // обратите внимание, что в качестве параметра мы передаем figure, по которому кликнули
        generateSlider(figure);
    });
}

Далее работаем внутри функции generateSlider:
function generateSlider(figure) {
    // код слайдера
}

Затемняем фон:
darkenBack();
function darkenBack() {
    // проверяем, имеется ли затемнение
    // если отсутствует, добавляем, в противном случае, удаляем
    if (document.querySelector(".darken") == null) {
        let div = document.createElement("div");
        div.className = "darken";
        document.body.appendChild(div);
    } else {
        let div = document.querySelector(".darken");
        document.body.removeChild(div);
    }
}

Мы будет выводить на экран по одному слайду. Не забываем, что переключение слайдов должно быть плавным. Этого легко добиться с помощью прозрачности и небольшого перехода (transition). Поэтому накладываем изображения друг на друга, размещаем их по центру, и делаем все изображения, кроме «кликнутого», прозрачными:
for (i = 0; i < figures.length; i++) {
    if (figures[i].hasAttribute("style")) {
        figures[i].removeAttribute("style");
    } else {
        figures[i].setAttribute("style", "margin: 0; width: auto; position: absolute; opacity: 0;");
    }
}

// кнопки генерируются каждый раз при открытии/закрытии слайдера
if (figure.hasAttribute("style")) {
    figure.style.opacity = 1;
    generateButtons();
} else generateButtons();

Далее создаем кнопки переключения слайдов и закрытия галереи. Код получился длинным и скучным (возможно, генерировать кнопки каждый раз при запуске слайдера, было не лучшей идеей):
Код создания кнопок:
function generateButtons() {
    if (document.querySelector(".buttons") == null) {
        // создаем контейнер для кнопок
        let buttons = document.createElement("div");
        buttons.className = "buttons";
        slider.appendChild(buttons);

        // создаем левую кнопку
        let leftButton = document.createElement("button");
        leftButton.className = "button left";
        let leftImg = document.createElement("img");
        leftImg.src = "https://thebestcode.ru/media/sliderGenerator/buttons/left.png";
        leftButton.appendChild(leftImg);
        buttons.appendChild(leftButton);
        leftButton.addEventListener("click", () => changeSlide("-"));

        // создаем правую кнопку
        let rightButton = document.createElement("button");
        rightButton.className = "button right";
        let rightImg = document.createElement("img");
        rightImg.src = "https://thebestcode.ru/media/sliderGenerator/buttons/right.png";
        rightButton.appendChild(rightImg);
        buttons.appendChild(rightButton);
        rightButton.addEventListener("click", () => changeSlide("+"));

        // создаем кнопку закрытия слайдера
        let closeButton = document.createElement("button");
        closeButton.className = "button close";
        let closeImg = document.createElement("img");
        closeImg.src = "https://thebestcode.ru/media/sliderGenerator/buttons/close.png";
        closeButton.appendChild(closeImg);
        buttons.appendChild(closeButton);
        closeButton.addEventListener("click", () => generateSlider(figure));
    } else {
        // если кнопки созданы, удаляем их
        let buttons = document.querySelector(".buttons");
        slider.removeChild(buttons);
    }
}


Переключение слайдов реализуется с помощью функции changeSlide, которой в качестве параметра передается, соответственно, "+" или "-":
function changeSlide(e) {
    // делаем все слайды прозрачными
    for (i = 0; i < figures.length; i++) {
        figures[i].style.opacity = 0;
    }
    if (e == "-") {
        // если текущий слайд является первым изображением, переключаем на последнее изображение
        if (figure == figures[0]) {
            figure = figures[figures.length - 1];
        } else {
            figure = figure.previousElementSibling;
        }
    } else if (e == "+") {
        // если текущий слайд является последним изображением, переключаемся на первое изображение
        if (figure == figures[figures.length - 1]) {
            figure = figures[0];
        } else {
            figure = figure.nextElementSibling;
        }
    }
    // текущий слайд делаем непрозрачным
    figure.style.opacity = 1;
}

Добавляем возможность переключения слайдов и закрытия галереи с помощью клавиатуры:
document.addEventListener("keydown", e => {
    // стрелка влево
    if (e.keyCode == 37 || e.keyCode == 189) {
        changeSlide("-");
    // стрелка вправо
    } else if (e.keyCode == 39 || e.keyCode == 187) {
        changeSlide("+");
    // esc
    } else if(e.keyCode == 27) {
        generateSlider(figure);
    }
});

Вот и все, генератор адаптивной галереи со встроенным слайдером готов. Задача выполнена. Условия соблюдены. Ближе к концу понял, что «минимум кода» и «вся разметка формируется на лету с помощью JS» противоречат друг другу, но было уже поздно (it's too late to apologize или как там у One Republic?).

Результат можно посмотреть здесь.

Обратите внимание, что на Codepen мы используем URL.createObjectURL для формирования ссылок на изображения, потому что Codepen не видит папку img.

Благодарю за внимание.
Tags:
Hubs:
Total votes 12: ↑11 and ↓1+10
Comments5

Articles