Как стать автором
Обновить

Создание квадратизированной галереи проектов на JS v 2.0

Время на прочтение9 мин
Количество просмотров2.9K
Под квадратизированной галереей подразумевается галерея такого типа
Под квадратизированной галереей подразумевается галерея такого типа

Предпосылки / проблема

При работе над своим проектом вручную создавал стилизованную галерею проектов и понял, что бесконечно плодить громоздкие однотипные HTML-контейнеры – это очень сложный и долгий путь. Захотелось данную задачу автоматизировать и решить более элегантно через JS, описывая каждую картинку как объект на основе класса. Это вторая статья на эту тему, здесь я постарался исправить некоторые ошибки и поработал над логикой и структурой кода. Для удобства добавил возможность просмотра галереи и полного кода в песочнице.

Основные принципы и задачи

  • Создать js-класс ImgComponent, описывающий компонент картинки галереи и принимающий следующие аргументы: тип картинки - широкая, высокая, стандартная, ссылка на изображение, ссылка на проект.

  • Генерировать галерею в html.

  • Реализовать механизм показа/скрытия частей галереи. После полного показа галереи - кнопка должна скрываться.

  • При наведении на картинку поверх изображения должна накладываться подсказка - заголовок проекта и прочая информация.

  • По class.elem должен быть доступен корневой DOM элемент картинки.

В статье пошагово описываю все свои действия по каждому пункту. Для создания данной галереи вам понадобится базовое знание ООП в JS и grid в CSS.

Приступим к созданию

1. Создадим HTML-контейнер для галереи:

<div class="gallery">
	<!--Сюда будут интерпретироваться картинки-->
</div>

2. Пропишем CSS для галереи и картинок:

Стили для высокой, широкой и стандартной картинки:

.short_box {
  width: 383px;
  height: 337px;
  background: #ffffff;
}
	
.high_box {
  width: 383px;
  height: 692px;
  background: #ffffff;
}
	
.long_box {
  width: 795px;
  height: 337px;
  background: #ffffff;
  grid-column: 1/2;
}

Стили для всех картинок:

.gallery > div {
  border: 4px solid #BB70B3;
  z-index: 10;
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.gallery > div img {
  z-index: -5;
  width: 100%;
  height: auto;
  position: absolute;
  left: 50%; 
  top: 50%;
  transform: translate(-50%, -50%);
}

Стили для контейнера галереи:

.gallery {
  width: 805px;
  display: grid;
  grid-template-columns: repeat(2, 390px);
  grid-gap: 20px;
  margin: 0 auto;
  justify-content: center;
  margin-top: 50px;
  margin-bottom: 50px;
}

Стили для кнопки прокрутки:

.scroll_button {
  width: 200%;
  cursor: pointer;
  grid-column: 1/2;
  padding: 0 10px 0 10px;
  position: relative;
}

Стили для анимации при наведении на кнопку:

@keyframes scroll_move {
  0% {
    top: 0;
  }
  100% {
    top: 12px;
  }
}  
.scroll_button:hover {
  animation-name: scroll_move;
  animation-duration: 0.5s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-out;
  animation-direction: alternate;
}

Стили для анимации подсказки при наведении на картинку - маска, заголовок, текст

.mask {
  width: 100%;
  height: 100%;
  background: rgba(176, 155, 174, 0.92);
  position: absolute;
  display: none;
}

.descr {
  position: absolute;
  top: 35%;
  display: none;
  margin-left: 26px;
}

.gallery > div:hover .mask {
  display: block;
}
.gallery > div:hover .descr {
  display: block;
  top: 50%;
  transform: translate(0%, -50%);
}

.gallery h2 {
  font-family: Nunito;
  font-style: normal;
  font-weight: normal;
  font-size: 24px;
  line-height: 33px;
  letter-spacing: 0.2em;
  color: #FFFFFF;
}
.gallery p {
  font-family: Nunito;
  font-style: normal;
  font-weight: normal;
  font-size: 11px;
  line-height: 15px;
  letter-spacing: 0.2em;
  color: #F3F3F3;
  padding-top: 7px;
}

Дополнительные стили для адаптивности галереи:

@media screen and (max-width: 795px) {
  .gallery {
    width: 595px;
    grid-template-columns: 1fr;
  }
  .short_box {
    width: 283px;
    height: 237px;
    background: #ffffff;
  }
  .high_box {
    width: 283px;
    height: 490px;
    background: #ffffff;
  }
  .long_box {
    width: 595px;
    height: 237px;
    background: #ffffff;
  }
  .high_box, .long_box, .short_box {
    grid-column: 1!important;
    grid-row: auto!important;
    justify-self: center;
  }
  .scroll_button {
    grid-column: 1;
    width: 100%;
    padding: 0;
  }
}

@media screen and (max-width: 595px) {
  .gallery {
    width: 100%;
  }
  .long_box {
    width: 100%;
    height: 237px;
  }
}

3. Пишем JS-класс для создания компонентов:

Создадим класс, в котором будем создавать DOM-элемент и повесим на него событие “onclick” для перехода по нужной ссылке. Для будущего механизма показа/скрытия частей галереи передадим аргумент groupImg, в который впоследствии будет передаваться группа с принадлежащим ей изображением. Благодаря этому мы сможем скрывать и показывать нужные нам группы изображений.

В аргумент descrip передаем объект с двумя свойствами: header - заголовок, text - текст подсказки.

class ImgComponent {
    constructor(groupImg, srcImg, srcToProject, row, column, size, descrip) { // передаем аргументы: группа изображений, ссылка на изображение, ссылка на проект, значение свойства row, значение свойства column, размер картинки по заранее созданным типам(short, long, high), объект подсказки

        this.render(); //создаем корневой DOM элемент

        this.elem.className = `img_gallery ${size}_box`; // присваиваем классы для картинок, и также класс описывающий его тип
        this.elem.dataset.group = `${groupImg}`; // Присваиваем data-атрибут 
        this.elem.href = `./articles/${srcToProject}`; // Присваиваем ссылку на проект, на который ведет картинка
        this.elem.style.cssText = `
            grid-row: ${size === 'high' ? row + '/' + ++row : row};
            grid-column: ${size === 'long' ? '1/2' : column + '/' + column};
        `; // Присваиваем все нужные свойства.
        this.elem.innerHTML = `
            <div class="mask"></div>
            <img src="img/${srcImg}" alt="gallery_img">
            <div class="descr">
                <h2>${descrip.header}</h2>
                <p>${descrip.text}</p>
            </div>
        `; // Вкладываем внутрь элемента картинку, маску и подсказку
        this.appendElem(); // Добавляем элемент на страницу
        this.onClick(); // Добавляем на элемент событие onclick
    } 
    render = () => { 
        this.elem = document.createElement('div'); //создаем метод, создающий корневой DOM элемент нашей картинки.
    }
    appendElem = () => { // Создаем метод, для добавления элемента на страницу
        document.querySelector('.gallery').append(this.elem);
    }
    onClick = () => { // Создаем метод для прослушки события onclick элемента, для перехода по ссылке.
        this.elem.addEventListener('click', () => {
            window.open(this.elem.href);
        })
    }
}

Класс для генерации галереи готов, теперь можем создавать на основе него нужные нам компоненты (картинки).

new ImgComponent(1, 'img_1.jpg', 'article1.html', 1, 1, 'short', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_1.jpg', 'article1.html', 1, 2, 'short', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_3.jpg', 'article1.html', 2, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_2.jpg', 'article1.html', 3, 1, 'high', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_2.jpg', 'article1.html', 3, 2, 'high', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(2, 'img_3.jpg', 'article1.html', 4, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(2, 'img_3.jpg', 'article1.html', 5, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(3, 'img_3.jpg', 'article1.html', 6, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(3, 'img_3.jpg', 'article1.html', 7, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});

Далее создадим объект, на основе которого будет создаваться элемент нашей кнопки, и все необходимые методы.

let scrollButton = {
    elem: document.createElement('img'), // Создаем корневой DOM элемент кнопки
    groupNum: 1, // Создаем свойство, которая будет считать прогресс показа картинок
    hiddenImg() {  // Создаем метод скрытия для первоначального всех изображений
        let array = document.querySelectorAll('.img_gallery'); // Выделяем все изображения внутри галлереи
        let arrayShow = document.querySelectorAll(`.img_gallery[data-group="${this.groupNum}"]`); // выделяем первую группу изображений по атрибута data-group для показа
        for(let i = 0; i < array.length; i++) { // Скрываем все изображения
            array[i].style.display = 'none';
        }
        for(let i = 0; i < arrayShow.length; i++) { // Показ первой группы изображений
            arrayShow[i].style.display = '';
            if(i === arrayShow.length - 1) { // Вставка элементов после первой группе изображений
                arrayShow[i].insertAdjacentElement('afterEnd', this.elem);
            }
        }
    },
    showImg() { // Создаем метод показа следующей группы изображений
        let array = document.querySelectorAll('.img_gallery'); // Выделяем все изображения внутри галлереи
        let arrayShow = document.querySelectorAll(`.img_gallery[data-group="${this.groupNum}"]`); // выделяем следующую группу изображений по атрибута data-group для показа
        for(let i = 0; i < arrayShow.length; i++) { // Перебор массива изображений которые нужно показать
            arrayShow[i].style.display = ''; // показываем все изображения из нашего массива 
            if(arrayShow[i] !== array[array.length - 1]) { // Проверка - на то является ли группа картинок последней
                arrayShow[i].insertAdjacentElement('afterEnd', this.elem); // Если нет, то вставляем кнопку после следующей группы картинок
            } else {
                this.elem.style.display = 'none'; // Если условие не проходит, скрываем кнопку со страницы
            }
        }
    },
    render() {
        this.elem.src = './img/scroll.svg'; // Прописываем путь до кнопки показа
        this.elem.className = 'scroll_button'; // Присваивает класс, для последующего приминения CSS стилей
        this.hiddenImg(); // Скрытие всех изображений на странице, кроме первой группы

        this.elem.addEventListener('click', () => { // Вешаем событие на кнопку - показ группы изображений и увелечение переменной хранящей в себе прогресс показа изображений
            this.groupNum++;
            this.showImg();
        })
    }
}

Завершающий штрих! Вызовем метод render() для активации механизма.

scrollButton.render();
Полный JS код
class ImgComponent {
    constructor(groupImg, srcImg, srcToProject, row, column, size, descrip) {

        this.render();

        this.elem.className = `img_gallery ${size}_box`;
        this.elem.dataset.group = `${groupImg}`;
        this.elem.href = `./articles/${srcToProject}`;
        this.elem.style.cssText = `
            grid-row: ${size === 'high' ? row + '/' + ++row : row};
            grid-column: ${size === 'long' ? '1/2' : column + '/' + column};
        `;
        this.elem.innerHTML = `
            <div class="mask"></div>
            <img src="img/${srcImg}" alt="gallery_img">
            <div class="descr">
                <h2>${descrip.header}</h2>
                <p>${descrip.text}</p>
            </div>
        `;
        this.appendElem();
        this.onClick();
    } 
    render = () => { 
        this.elem = document.createElement('div');
    }
    appendElem = () => { 
        document.querySelector('.gallery').append(this.elem);
    }
    onClick = () => { 
        this.elem.addEventListener('click', () => {
            window.open(this.elem.href);
        })
    }
}

new ImgComponent(1, 'img_1.jpg', 'article1.html', 1, 1, 'short', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_1.jpg', 'article1.html', 1, 2, 'short', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_3.jpg', 'article1.html', 2, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_2.jpg', 'article1.html', 3, 1, 'high', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(1, 'img_2.jpg', 'article1.html', 3, 2, 'high', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(2, 'img_3.jpg', 'article1.html', 4, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(2, 'img_3.jpg', 'article1.html', 5, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(3, 'img_3.jpg', 'article1.html', 6, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});
new ImgComponent(3, 'img_3.jpg', 'article1.html', 7, 1, 'long', {'header': 'Head', 'text': 'Test text test text test text'});


let scrollButton = {
    elem: document.createElement('img'),
    groupNum: 1,
    hiddenImg() {
        let array = document.querySelectorAll('.img_gallery');
        let arrayShow = document.querySelectorAll(`.img_gallery[data-group="${this.groupNum}"]`);
        for(let i = 0; i < array.length; i++) {
            array[i].style.display = 'none';
        }
        for(let i = 0; i < arrayShow.length; i++) {
            arrayShow[i].style.display = '';
            if(i === arrayShow.length - 1) {
                arrayShow[i].insertAdjacentElement('afterEnd', this.elem);
            }
        }
    },
    showImg() {
        let array = document.querySelectorAll('.img_gallery');
        let arrayShow = document.querySelectorAll(`.img_gallery[data-group="${this.groupNum}"]`);
        for(let i = 0; i < arrayShow.length; i++) {
            arrayShow[i].style.display = '';
            if(arrayShow[i] !== array[array.length - 1]) {
                arrayShow[i].insertAdjacentElement('afterEnd', this.elem);
            } else {
                this.elem.style.display = 'none';
            }
        }
    },
    render() {
        this.elem.src = './img/scroll.svg';
        this.elem.className = 'scroll_button';
        this.hiddenImg();

        this.elem.addEventListener('click', () => {
            this.groupNum++;
            this.showImg();
        })
    }
}

scrollButton.render();
Полный CSS код
@keyframes scroll_move {
  0% {
    top: 0;
  }
  100% {
    top: 12px;
  }
}  

.short_box {
  width: 383px;
  height: 337px;
  background: #ffffff;
}
	
.high_box {
  width: 383px;
  height: 692px;
  background: #ffffff;
}
	
.long_box {
  width: 795px;
  height: 337px;
  background: #ffffff;
  grid-column: 1/2;
}

.gallery > div {
  border: 4px solid #BB70B3;
  z-index: 10;
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.gallery > div img {
  z-index: -5;
  width: 100%;
  height: auto;
  position: absolute;
  left: 50%; 
  top: 50%;
  transform: translate(-50%, -50%);
}

.gallery {
  width: 805px;
  display: grid;
  grid-template-columns: repeat(2, 390px);
  grid-gap: 20px;
  margin: 0 auto;
  justify-content: center;
  margin-top: 50px;
  margin-bottom: 50px;
}

.scroll_button {
  width: 200%;
  cursor: pointer;
  grid-column: 1/2;
  padding: 0 10px 0 10px;
  position: relative;
}

.scroll_button:hover {
  animation-name: scroll_move;
  animation-duration: 0.5s;
  animation-iteration-count: infinite;
  animation-timing-function: ease-out;
  animation-direction: alternate;
}

.mask {
  width: 100%;
  height: 100%;
  background: rgba(176, 155, 174, 0.92);
  position: absolute;
  display: none;
}

.descr {
  position: absolute;
  top: 35%;
  display: none;
  margin-left: 26px;
}

.gallery > div:hover .mask {
  display: block;
}
.gallery > div:hover .descr {
  display: block;
  top: 50%;
  transform: translate(0%, -50%);
}

.gallery h2 {
  font-family: Nunito;
  font-style: normal;
  font-weight: normal;
  font-size: 24px;
  line-height: 33px;
  letter-spacing: 0.2em;
  color: #FFFFFF;
}
.gallery p {
  font-family: Nunito;
  font-style: normal;
  font-weight: normal;
  font-size: 11px;
  line-height: 15px;
  letter-spacing: 0.2em;
  color: #F3F3F3;
  padding-top: 7px;
}

@media screen and (max-width: 795px) {
  .gallery {
    width: 595px;
    grid-template-columns: 1fr;
  }
  .short_box {
    width: 283px;
    height: 237px;
    background: #ffffff;
  }
  .high_box {
    width: 283px;
    height: 490px;
    background: #ffffff;
  }
  .long_box {
    width: 595px;
    height: 237px;
    background: #ffffff;
  }
  .high_box, .long_box, .short_box {
    grid-column: 1!important;
    grid-row: auto!important;
    justify-self: center;
  }
  .scroll_button {
    grid-column: 1;
    width: 100%;
    padding: 0;
  }
}

@media screen and (max-width: 595px) {
  .gallery {
    width: 100%;
  }
  .long_box {
    width: 100%;
    height: 237px;
  }
}

Полный код галереи в песочнице - песочница

P.S. Если у вас есть идеи для дополнения или улучшения моей галереи, пишите в комментариях, я обязательно это реализую.

Теги:
Хабы:
Всего голосов 2: ↑1 и ↓10
Комментарии0

Публикации

Истории

Работа

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область