
Доброго времени суток, друзья!
Вместо введения (постановка задачи)
Все началось с изучения чужих слайдеров (готовых решений в сети, типа 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.
Благодарю за внимание.
