Как сверстать 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. Мы научились строить сложные сетки, объединять колонки, работать с карточками поверх изображений, использовать псевдоэлементы для затемнения и создавать современный интерфейс, который легко масштабируется и выглядит профессионально.

Решение задачи

предыдущий пост следующий пост