Все началось с изучения чужих слайдеров (готовых решений в сети, типа 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/*», означающее, что можно загружать только изображения (любые).
Сразу наведем красоту (добавим стили):
Тут даже говорить не о чем (.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();
Далее создаем кнопки переключения слайдов и закрытия галереи. Код получился длинным и скучным (возможно, генерировать кнопки каждый раз при запуске слайдера, было не лучшей идеей):
Код создания кнопок:
Переключение слайдов реализуется с помощью функции 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.