Как сверстать Bento-галерею на CSS Grid
Эта статья основана на одном из практических заданий verstaem.online.
Вы можете читать материал как обычное руководство или сначала попробовать выполнить задание самостоятельно, а затем сравнить свое решение с подробным разбором.
Перейти к заданиюВ этой статье мы создадим современную Bento-галерею с карточками разных размеров. На ее примере познакомимся с более продвинутыми возможностями CSS Grid, научимся объединять колонки, делать карточки с изображениями и затемнением, а также разберем, как строятся подобные интерфейсы, которые сегодня можно встретить практически в каждом современном приложении.
Кстати, почему «bento»? Название пришло из японских бенто-боксов, где разные продукты разложены по секциям разных размеров. Собственно, в нашем дизайне похожая история.
Создаем HTML-разметку
Сначала создадим базовую HTML-разметку с помощью Emmet. Для этого достаточно ввести ! и нажать Tab. Обратите внимание на атрибут lang. Поскольку основной контент страницы написан на русском языке, здесь используем значение ru.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bento-галерея</title>
</head>
<body>
</body>
</html>
Итак, давайте разберемся с базовой разметкой. Сделаем див, а в нем контейнер, чтобы выровнять все по центру. Мы сразу сюда добавляем класс grid, чтобы управлять сеткой.
<div class="content">
<div class="container grid">
</div>
</div>
Левая колонка
Слева располагаем колонку с помощью aside, и добавим внутрь три основных элемента — заголовок, описание, и список тегов. Обратите внимание, что emoji в браузере не совпадают с теми, что в макете. Это нормально, ничего страшного.
<aside class="content__aside">
<h1 class="content__title">
Откройте природу
</h1>
<p class="content__descr">
Просматривайте места, отобранные фотографами со всего мира. Исследуйте горы, леса, пляжи и города.
</p>
<div class="content__tags">
<div class="content__tag">
🏔 Горы
</div>
<div class="content__tag">
🌊 Океан
</div>
<div class="content__tag">
🌲 Лес
</div>
<div class="content__tag">
🏙 Города
</div>
<div class="content__tag">
🌵 Пустыня
</div>
</div>
</aside>
Теги можно было бы сделать списком, но эти элементы не имеют интерактивности, поэтому это можно оставить через <div>.
Галерея
А вот галерею логично сделать именно списком. Список будет управлять расположением элементов, а внутри будут карточки, сделаем их с помощью <div>. Внутри карточки будет картинка, название и описание.
<div class="content__gallery">
<ul class="gallery">
<li class="gallery__item gallery__item--big">
<div class="gallery-card">
<img src="img/1.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Швейцарские альпы
</h2>
<p class="gallery-card__descr">
42 фотографии
</p>
</div>
</li>
<li class="gallery__item">
<div class="gallery-card">
<img src="img/2.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Океан
</h2>
<p class="gallery-card__descr">
18 фотографий
</p>
</div>
</li>
<li class="gallery__item">
<div class="gallery-card">
<img src="img/3.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Лес
</h2>
<p class="gallery-card__descr">
27 фотографий
</p>
</div>
</li>
<li class="gallery__item">
<div class="gallery-card">
<img src="img/4.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Токио
</h2>
<p class="gallery-card__descr">
31 фотография
</p>
</div>
</li>
<li class="gallery__item">
<div class="gallery-card">
<img src="img/5.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Пляж
</h2>
<p class="gallery-card__descr">
15 фотографий
</p>
</div>
</li>
<li class="gallery__item gallery__item--big">
<div class="gallery-card">
<img src="img/6.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Река
</h2>
<p class="gallery-card__descr">
56 фотографий
</p>
</div>
</li>
<li class="gallery__item gallery__item--big">
<div class="gallery-card">
<img src="img/7.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Пустыня
</h2>
<p class="gallery-card__descr">
13 фотографий
</p>
</div>
</li>
<li class="gallery__item gallery__item--big">
<div class="gallery-card">
<img src="img/8.png" aria-hidden="true" alt="" class="gallery-card__pic">
<h2 class="gallery-card__title">
Снег
</h2>
<p class="gallery-card__descr">
28 фотографий
</p>
</div>
</li>
</ul>
</div>
Несмотря на то, что я всегда обращаю ваше внимание на заполнение alt, здесь он не заполнен. Все потому, что изображения здесь служат как декоратив, как фон. И можно было бы сделать их именно через CSS, но я решил показать способ через html. Это не ошибка, это просто иное мышление. И здесь нам пригодится атрибут aria-hidden, чтобы скринридеры не читали это изображение, а вообще не замечали его. Также обратите внимание, что у некоторых <li> есть дополнительный класс gallery__item--big, он как раз нужен, чтобы разместить элементы в сетке так, как нам нужно.
Добавляем стили
Подключим файл стилей через <link> и зададим несколько базовых правил. Для этого задания достаточно переопределить box-sizing, сбросить отступ у <body> и подключить шрифт Inter, который используется в макете.
* {
box-sizing: border-box;
}
@font-face {
font-family: "Inter";
src: url("../fonts/InterRegular.woff2");
font-display: swap;
font-weight: 400;
}
@font-face {
font-family: "Inter";
src: url("../fonts/InterBold.woff2");
font-display: swap;
font-weight: 700;
}
body {
margin: 0;
padding: 20px;
background: #f4f6f9;
font-family: "Inter", sans-serif;
}
.container {
margin: 0 auto;
padding: 0 15px;
max-width: 1318px;
}
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
align-items: start;
gap: 20px;
}
Здесь мы добавляем контейнер для центрирования контента, а также добавляем grid, чтобы управлять сеткой. Указываем `repeat (12, 1fr) — все потому, что в нашем макете так и есть — 12 колонок. Это своего рода стандарт индустрии, 12 колонок — самое удобное число, которое делится на 1, 2, 3, 4, 6, 12. А вот говорить элементам, сколько колонок им занимать, мы будем ниже.
Стили левой части
Зададим стили сразу для левой колонки. Здесь все достаточно стандартно, выделяем только одну запись — grid-column: 4 span. Здесь мы задаем то количество колонок, которое должна занимать левая часть, из 12ти. Она будет занимать 4 колонки.
.content__aside {
grid-column: 4 span;
border-radius: 32px;
padding: 32px;
background: #fff;
}
.content__title {
margin: 0;
margin-bottom: 20px;
font-weight: 700;
font-size: 48px;
line-height: 120%;
color: #111827;
}
.content__descr {
margin: 0;
margin-bottom: 32px;
font-weight: 400;
font-size: 16px;
line-height: 140%;
color: #6b7280;
}
.content__tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.content__tag {
border-radius: 100px;
padding: 8px 12px;
font-weight: 400;
font-size: 12px;
line-height: 120%;
color: #111827;
background: #f3f4f6;
}
Стили правой части
Здесь все сложнее и интереснее. Конечно, нужно сделать размер для правой части через свой grid-column. Но тут есть другой интересный момент — нам нужно сделать сетку и для самого списка карточек. Поэтому мы снова используем grid, но уже с 8-ю колонками.
.content__gallery {
grid-column: 8 span;
}
.gallery {
display: grid;
grid-template-columns: repeat(8, 1fr);
margin: 0;
padding: 0;
list-style: none;
gap: 20px;
}
Конечно, тут можно было поставить и 12, но если в макете 8 — зачем усложнять? Далее, нам нужно расположить элементы. Большее количество карточек занимают 2 колонки, и лишь некоторые — 4. Так и укажем.
.gallery__item {
grid-column: 2 span;
}
.gallery__item--big {
grid-column: 4 span;
}
Стили карточки
Теперь остается дело техники — просто раздать стили элементам карточки. Но разумеется, без нюансов тут не обойдется.
.gallery-card {
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-end;
overflow: hidden;
border-radius: 24px;
padding: 16px;
min-height: 200px;
background: #fff;
}
Для начала скажем, что здесь задан overflow: hidden, чтобы изображение обрезалось и принимало тот же радиус, что и у родителя. Также здесь явно указан flexbox и justify-content: flex-end. Таким образом мы прижимаем весь контент карточки к нижнему краю, прямо как в макете.
В итоге получается классическая ситуация — Grid управляет «большой» сеткой, а Flex — внутренностями. Это очень часто происходит в реальных макетах.
Далее сделаем затемнение, и здесь пригодится все то же правило, что и раньше — если мы можем делать декор без вмешательства в html, нужно так и делать. Поэтому используем псевдоэлемент. Особое внимание обратите на inset: 0 и z-index: 1. Один растягивает псевдоэлемент на всю карточку, второй поднимает ее на слой выше. Это нужно, чтобы изображение было «под» затемнением.
.gallery-card::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(180deg, rgba(217, 217, 217, 0) 0%, rgba(0, 0, 0, 0.9) 100%);
}
Далее само изображение. Размещаем мы его также через position: absolute, чтобы оно встало именно как фон и не влияло на размеры карточки. Влиять на размеры всегда должен контент. Растягиваем также через inset и картинка встает так, будто это фон.
.gallery-card__pic {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
}
Теперь стили текста. Тут все просто, забираем все из макета. Но не забудем добавить position: relative и z-index: 2, именно 2, чтобы текст был еще выше, чем затемнение.
.gallery-card__title {
position: relative;
z-index: 2;
margin: 0;
margin-bottom: 4px;
font-weight: 700;
font-size: 16px;
color: #fff;
}
.gallery-card__descr {
position: relative;
z-index: 2;
margin: 0;
font-weight: 400;
font-size: 12px;
color: rgba(255, 255, 255, 0.8);
}
Теперь вы умеете создавать Bento-галереи с помощью CSS Grid. Мы научились строить сложные сетки, объединять колонки, работать с карточками поверх изображений, использовать псевдоэлементы для затемнения и создавать современный интерфейс, который легко масштабируется и выглядит профессионально.