Flexbox и Grid Layout

1. Flexbox — теория
До появления flexbox для управления потоком и построения сеток использовались табличная вёрстка (с таблицами нам ещё предстоит познакомиться), блочно-строчные элементы и float
. На данный момент эти техники очень сильно устарели и имеют множество проблем и ограничений.
Хотя сами CSS-свойства и HTML-элементы, затронутые выше, по-прежнему отлично решают те задачи, для которых они были созданы, вопросы построения аккуратных равноразмерных сеток и адаптивности веб-страницы стояли очень остро.
CSS Flexible Box Layout, или просто flexbox, был разработан как модель одномерного-направленного макета — в виде строки или столбца, позволяющая наиболее оптимально заполнять доступное пространство, автоматически выставляя ширину и высоту дочерних элементов.
Модель flexbox состоит из двух типов элементов: родительского — flex-контейнер, и дочерних — flex-элементы. Чтобы создать flex-контейнер, мы задаём значение flex
или inline-flex
для свойства display
контейнера. Как только мы это сделаем, прямые потомки этого контейнера станут flex-элементами.
В отличие от блочно-строчных между flex-элементами отсутствуют пробелы, что упрощает их расположение и расчёт ширины контейнера (в примерах ниже отступы будут оставлены намеренно для большей наглядности).
При этом, работая с flexbox, нужно мыслить с точки зрения двух осей – главной и побочной.
Главная ось
Главная ось задаёт направление потока flex-элементов и определяется свойством flex-direction, которое может принимать следующие значения:
row
— главная ось направлена так же, как и ориентация текста, по умолчанию слева направо. Элементы отображаются в ряд. Значение по умолчанию.row-reverse
— аналогично значениюrow
, но в обратном направлении: меняются местами начальная и конечная точки, а главная ось по умолчанию направлена справа налево.column
— главная ось располагается вертикально и направлена сверху вниз.column-reverse
— аналогично значениюcolumn
, но в обратном направлении: меняется положение начальной и конечной точек, а главная ось направлена снизу вверх.
flex-basis и свойства flex-элементов
Базовый размер флекс элемента по основной оси задаёт свойство flex-basis
. Значение по умолчанию — auto
. Если направление — строка, то в нормальных условиях свойство аналогично значению width
, если колонка — то height
.
Как видно из примеров выше, flex-элементы отображаются в ряд или колонку, начиная от начала или конца главной оси в зависимости от свойства flex‑direction
, при этом по умолчанию:
- не растягиваются по основной оси;
- растягиваются, чтобы заполнить побочную ось;
- не переносятся на новую строку (колонку);
- если места по основной оси окажется недостаточно, то flex-элементы сожмутся до своей минимальной ширины, а затем выпадут из контейнера.
Давайте зададим флекс-элементам { flex‑basis: 100px; }
и посмотрим на следующие примеры:



В первом примере ширина элементов составляет 100px
, во втором — элементы пропорционально сжались, чтобы поместиться в свой контейнер, в третьем — элементы сжались до своего минимума и затем выпали из контейнера, так как им не хватило места.
Элементы могут переноситься на новую строку. За это отвечает свойство flex-wrap, которое надо задать flex-контейнеру. Значения:
nowrap
— нет переноса, элементы расположены в одну строку. Контейнер может быть переполнен. Значение по умолчанию;wrap
— элементы переносятся на новые строки в направлении в соответствии со значениемflex‑direction
;wrap-reverse
— элементы переносятся на новые строки, но в обратном направлении.
Абсолютно аналогично флекс-элементы будут себя вести, если мы ограничим высоту контейнеру и зададим направление главной оси — в колонку:

Элементы переносятся в новую колонку и при этом стараются заполнить всё доступное пространство по побочной оси, перпендикулярной основной.
flex-grow и flex-shrink
По умолчанию размер флекс-элемента по главной оси зависит от контента или определяется значением flex-basis.
CSS-свойство flex-grow
определяет как много свободного пространства во flex-контейнере должно быть назначено текущему элементу. Принимает численные значения, в том числе и дробные, и по умолчанию равно нулю.
Если все флекс-элементы имеют одинаковый коэффициент flex‑grow
, то они получат одинаковую долю свободного пространства, в противном случае оно распределяется в соответствии с соотношением, определённым коэффициентами flex‑grow
для каждого элемента.


Помимо фактора растяжения существует и фактор сжатия элементов — CSS-свойство flex-shrink
, который будет определять заполнение контейнера в том случае, когда стандартная ширина флекс-элементов превышает ширину контейнера. Принимает численные значения, в том числе и дробные, и по умолчанию равно 1.

В этом примере ширина первого элемента — auto
, второго — 300px
. Однако размеров первого блока не хватило для вывода содержимого, и размеры флекс-элементов были автоматически пересчитаны. Но, прописав второму элементу { flex‑shrink: 0 }
, получим следующее:

Фактически, мы запретили сжатие для второго элемента, его ширина теперь действительно 300px
, а первый элемент вынужден был занять оставшееся место.
Существует упрощённая запись трех CSS-свойств в одном — свойство flex
, устанавливает flex‑grow
, flex‑shrink
и flex‑basis
. Значение по умолчанию: { flex: 0 1 auto }
.
Как видно из примеров выше, размеры наших блоков становятся адаптивными, подстраиваясь под своё содержимое или исходя из заданных им свойств. Всё это позволяет создавать очень гибкие и сложные сетки. Отлично подходит для создания различных листингов — например списка товаров, когда мы хотим получить карточки одной ширины и высоты в каждой строке, не задавая им при этом жёстко высоту, — ведь контент в каждой карточке может отличаться по объёму. С помощью инлайн-блоков подобное сделать невозможно.
justify-content
CSS свойство justify-content
определяет, как браузер распределяет флекс-элементы вдоль главной оси флекс-контейнера после того, как были рассчитаны их размеры и отступы. Задаётся непосредственно флекс-контейнеру.
Это свойство также работает и с grid-сеткой, которую мы будем разбирать далее, поэтому может возникнуть некоторая путаница из применяемых значений. Рассмотрим именно для flexbox. Возможные значения:
flex-start
— значение по умолчанию, элементы располагаются в начале контейнера:flex-end
— элементы располагаются в конце контейнера:center
— элементы располагаются в центре контейнера:space-between
— элементы равномерно распределяются по всей строке. Первый и последний элементы прижимаются к соответствующим краям контейнера:space-around
— элементы равномерно распределяются по всей строке. Перед первым и после последнего элемента имеется пустое пространство, равное половине пространства между двумя соседними элементами:space-evenly
— элементы равномерно распределяются по всей строке. Пустое пространство в начале и конце строки равно пространству между двумя соседними элементами.
align-items и align-self
Выравнивать flex-элементы можно не только по главной, но и по побочной оси, в перпендикулярном направлении.
CSS-свойство align-items
выравнивает все flex-элементы текущей flex-линии по побочной оси, задаётся flex-контейнеру. Значения:
stretch
— значение по умолчанию, элементы растягиваются таким образом, чтобы занять всё доступное пространство контейнера;flex-start
— элементы располагаются в начале побочной оси контейнера;flex-end
— элементы располагаются в конце побочной оси контейнера;center
— элементы располагаются в центре побочной оси контейнера;baseline
— элементы выравниваются по базовой линии строки.
CSS-свойство align-self
выравнивает по побочной оси непосредственно сам flex-элемент, которому это свойство задано. Значение по умолчанию — auto
, не влияет на расположение flex-элемента. Остальные значения соответствуют свойству align‑items
.

flexbox и внешние отступы
Выстраивать flex-элементы по осям можно не только с помощью свойств justify‑content
, align‑items
, align‑self
, но и внешними отступами в значении auto, причём работает это с обеими осями:

align-content
Flexbox позволяет выравнивать не только элементы, но и строки внутри flex-контейнера по побочной оси. За это отвечает CSS-свойство align-content
, задаваемое flex-контейнеру.
Значения:
stretch
— Значение по умолчанию. Строки равномерно растягиваются, заполняя свободное пространство:flex-start
— Строки располагаются в начале побочной оси:flex-end
— Строки располагаются в конце побочной оси:center
— Строки располагаются в центре побочной оси:space-between
— Строки равномерно распределяются вдоль побочной оси с равными отступами. Первая строка располагается у начала побочной оси, последняя — в её конце:space-around
— Строки равномерно распределяются вдоль побочной оси с равными отступами. Отступ перед первой строкой и после последней равны половине отступа между двумя соседними строками;space-evenly
— Строки равномерно распределяются вдоль побочной оси с равными отступами. Отступ перед первой строкой и после последней равны отступу между двумя соседними строками.
order
order
— CSS-свойство, определяющее порядок, в котором располагаются flex-элементы в их flex-контейнере вдоль главной оси. Может принимать любые числа, в том числе и отрицательные. Положительные числа сдвигают элемент к концу оси, отрицательные — к началу. Элементы с одинаковым значением order
располагаются в том порядке, в каком они находятся в разметке HTML-документа. Значение по умолчанию — 0
.
2. Flexbox — вёрстка макета
Итак, к данному моменту мы изучили основы, которые позволят нам приступить к реальной вёрстке макета.
Flexbox Layout достаточно прост и позволяет закрыть большую часть наших потребностей. Поэтому именно его мы в первую очередь и будем использовать для построения наших сеток.
Давайте откроем макеты нашего учебного проекта и внимательно их рассмотрим. Мы видим три страницы, выполненные в адаптиве — для каждой из них отрисовано сразу четыре макета разной ширины. К адаптиву мы ещё вернёмся, а сейчас нас интересуют только самые широкие десктоп-макеты.
Далее последовательно воспроизводите все действия в своём учебном проекте.
Структура страницы
Как мы можем видеть, на всех трёх страницах имеются одинаковые элементы — шапка и футер. С них и начнём.
Первое, что необходимо вспомнить — есть элементы, для которых браузеры автоматически проставляют отступы, которые нам могут быть не нужны. Один из таких элементов — <body>
.
Хорошей практикой считается сбросить дефолтные стили сразу для <html>
и <body>
, чтобы гарантировать одинаковое поведение во всех браузерах:
html,
body {
width: 100%;
margin: 0;
padding: 0;
}
Здесь стоит обратить внимание, что общая высота футера и шапки нашего сайта составляет всего лишь 602px
. Это означает, что если на нашей странице будет совсем немного основного контента — например заголовок и пара абзацев текста, то итоговая высота нашего документа окажется меньше высоты вьюпорта браузера, а под футером останется пустое место, что будет выглядеть очень некрасиво.
Необходимо «прибить» наш футер к низу экрана. Сделать это можно разными способами. Сделаем это с помощью flexbox. Ранее мы уже должны были выделить основные блоки на главной странице. Получиться должно было примерно следующее:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Lenni Art</title>
<link rel="stylesheet" href="styles/styles.css">
</head>
<body class="body">
<header class="page-header"></header>
<main class="main"></main>
<footer class="page-footer"></footer>
</body>
</html>
Наша задача сделать так, чтобы <body>
по высоте был не менее области просмотра, а <main>
автоматически растягивался, занимая всё доступное пространство. При этом шапка у нас будет фиксированная и не занимать место в общем потоке. А это значит, что мы должны для <body>
ещё задать вертикальный отступ сверху на величину высоты шапки. Давайте всё это сделаем:
.body {
box-sizing: border-box;
display: flex;
flex-direction: column;
min-height: 100vh;
padding-top: 147px;
}
.page-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 147px;
}
.main {
flex: 1 0 auto;
}
.page-footer {
flex: 0 0 auto;
}
Итоговый результат получится следующим:
Шапка сайта
Теперь давайте полноценно сверстаем шапку нашего сайта. Первое, на что мы должны обратить внимание, элементы шапки занимают не всю ширину, а только 1334px
, располагаясь по центру. Таким образом внутри шапки нам понадобится дополнительная обёртка-контейнер. Сразу стоит отметить, что контейнер нужно всегда делать шире, чем он отрисован, компенсируя «лишнюю ширину» внутренними горизонтальными отступами. Это нужно для того, чтобы при уменьшении вьюпорта наши элементы не прилипли к краю экрана.
<header class="page-header">
<div class="page-header__container"></div>
</header>
.page-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 147px;
display: flex;
align-items: center;
background-color: rgba(222, 222, 222, 0.5);
backdrop-filter: blur(20px);
z-index: 100;
}
.page-header__container {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
max-width: 1414px;
margin: 0 auto;
padding-left: 40px;
padding-right: 40px;
}
Помимо позиционирования мы прописали стили фона шапки, которые можем найти во вкладке «Inspect» в правой части экрана. И здесь нас встречает новое CSS-свойство backdrop-filter
, визуально размывающая то, что находится у нас под шапкой. Однако мы имеем проблему — плохую поддержку данного свойства браузерами. Так, данное свойство, например, не будет работать в браузере Firefox. Проблему усугубляет то, что затемнение фона при неработающем свойстве недостаточно, и при прокрутке страницы шапка и то, что под ней, будут сливаться, сильно затрудняя восприятие информации.

Эта проблема может быть решена с помощью правила @supports
:
.page-header {
background-color: rgba(222, 222, 222, 0.98);
}
@supports (backdrop-filter: blur(20px)) {
.page-header {
background-color: rgba(222, 222, 222, 0.5);
backdrop-filter: blur(20px);
}
}
Теперь по умолчанию наша шапка будет иметь практически непрозрачный фон, однако если backdrop-filter
поддерживается браузером, то будет применено иное правило.
Далее мы видим логотип. Мы можем сделать экспорт лого из Figma в любом удобном для нас формате. Но для любых иконок и всегда по возможности предпочтительнее SVG. Почему — разберём на одном из следующих уроков, а пока просто отметим это для себя. Выделим лого и в правой части экрана нажмём на «Export«.

SVG-файл мы можем открыть любым текстовым редактором и затем просто вставить в разметку HTML-документа. Как правило, логотипы в шапке сайта являются ссылками, ведущими на главную страницу. Таким образом разметка будет выглядеть так:
<header class="page-header">
<div class="page-header__container">
<a href="index.html" class="page-header__logo">
<svg
class="page-header__logo-icon"
width="202"
height="147"
viewBox="0 0 202 147"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<!-- содержимое свг -->
</svg>
</a>
</div>
</header>
Добавим стили для нашего логотипа:
.page-header__logo {
flex-shrink: 0;
width: 202px;
height: 147px;
font-size: 0;
text-decoration: none;
}
.page-header__logo-icon {
width: 100%;
height: 100%;
}
SVG-изображение автоматически отцентруется в своих размерах.
Примечание: В качестве иконки мы можем использовать тег <img>
, задав путь к соответствующему файлу:
<a href="index.html" class="page-header__logo">
<img src="assets/icons/logo.svg" alt="logo" class="page-header__logo-icon">
</a>
На первый взгляд так поступать намного лучше, ведь HTML-разметки разительно меньше.
Однако на самом деле такой подход несёт много минусов с точки зрения оптимизации кода и ресурсов и даёт нам меньше возможностей с точки зрения визуальной составляющей и анимации. Самое простое и очевидное — отсутствие возможности сменить цвет иконки при наведению на элемент, ведь это просто внешний файл.
К этому мы ещё вернёмся в дальнейшем, а проблему большого количества разметки решим уже на следующем уроке, когда речь пойдёт об автоматизации разработки.
Вернёмся к нашему макету.
Далее мы видим главный навигационный блок. В соответствии с методологией БЭМ он не является отдельным независимым блоком, т.к. не может существовать вне шапки.
<nav class="page-header__nav">
<a href="#" class="page-header__nav-link">Мероприятия</a>
<a href="#" class="page-header__nav-link">Блог</a>
<a href="#" class="page-header__nav-link">О нас</a>
<a href="#" class="page-header__nav-link">Контакты</a>
</nav>
.page-header__nav {
display: flex;
align-items: baseline;
margin: 0 auto;
}
.page-header__nav-link {
margin: 0 25px;
color: #1f1e1e;
font-weight: 600;
font-size: 18px;
line-height: 25px;
text-decoration: none;
transition: color 0.2s;
}
.page-header__nav-link:hover {
color: #bc3324;
}
Здесь мы добавили первый текстовый контент — ссылки, и можем увидеть, что внешний вид их отличен от того, что нарисовано в макете. Причина — мы не подключили нужные шрифты. Для наших целей прекрасно подойдёт сервис Google Fonts, им и воспользуемся. Найдём нужный шрифт и подключим его в <head>
. Кроме того не забудем задать стили.
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&family=Open+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
.body {
color: #1F1E1E;
font-family: "Open Sans", sans-serif;
font-size: 16px;
font-weight: 400;
font-style: normal;
line-height: 1.375;
}
Мы прописали не только шрифт, но и прочие стили «по умолчанию» для текстового содержимого нашего сайта.
Наконец, справа мы видим кнопку «Войти». Данная кнопка одновременно является элементом БЭМ-блока .page-header
и самостоятельным переиспользуемым БЭМ-блоком.
<a href="#" class="btn page-header__user-btn">
Войти
</a>
.btn {
display: inline-block;
box-sizing: border-box;
padding: 15px 40px;
border: 1px solid #1F1E1E;
background-color: transparent;
outline: none;
color: #1F1E1E;
font-family: "Open Sans", sans-serif;
font-weight: 600;
font-size: 16px;
line-height: 20px;
text-align: center;
text-decoration: none;
transition: color 0.2s, background-color 0.2s, border-color 0.2s;
overflow: hidden;
cursor: pointer;
}
.btn:hover {
background-color: #1F1E1E;
color: #ffffff;
}
Мы получили практически то, что изображено на макете. Но присмотритесь — наш навигационный блок расположен левее, чем на макете, не по центру шапки. Это произошло по той причине, что ширина логотипа и кнопки «Войти» отличаются. Решить проблему можно разными способами. В рамках курса сделаем просто: обернём кнопку «Войти» в <div>
, задав ему ту же ширину, что и у логотипа.
<div class="page-header__right-block">
<a href="#" class="btn page-header__user-btn">
Войти
</a>
</div>
.page-header__right-block {
display: flex;
justify-content: flex-end;
align-items: center;
width: 202px;
}
Вот теперь выглядит всё как нужно:
Примечание: в предоставленном выше живом примере есть некоторые отличия в стилях и разметке, допущенные для корректного отображения в онлайн-песочнице.
Листинг мероприятий
Теперь давайте обратим своё внимание на второй макет и сверстаем листинг мероприятий.

Рядом с файлом index.html создайте файл events.html, скопировав из первого всю разметку, но оставив <main>
пустым. Далее создадим в нашем мэйне новыe БЭМ-блоки:
<main class="main">
<!-- Здесь будут хлебные крошки -->
<div class="events">
<section class="listing events__listing">
<h2 class="listing__title section-title">Мероприятия</h2>
<div class="listing__events-list">
<article class="event-card listing__event-card">
<!-- Карточка товара -->
</article>
</div>
</section>
<!-- Здесь будут дополнительные услуги -->
</div>
</main>
Как видно из разметки, мы выделили .listing
в отдельный БЭМ-блок, так как он легко может быть использован на любой другой странице при необходимости. Давайте зададим основные стили нашему листингу и добавим новый переиспользуемый БЭМ-блок .section‑title
, который при этом является БЭМ-элементом блока .listing
.
Наши стили:
.section-title {
box-sizing: border-box;
display: flex;
width: 100%;
max-width: 624px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
max-width: 624px;
min-height: 62px;
margin: 0 auto 150px;
padding: 8px 45px;
background-color: #1F1E1E;
color: #ffffff;
font-family: "Lora", serif;
font-weight: 500;
font-size: 36px;
line-height: 1.277;
text-align: center;
text-transform: uppercase;
}
.listing {
box-sizing: border-box;
display: flex;
flex-direction: column;
width: 100%;
max-width: 1414px;
margin: 0 auto;
padding-left: 40px;
padding-right: 40px;
}
.listing__events-list {
display: flex;
flex-wrap: wrap;
}
.events {
padding: 140px 0 100px;
}
.events__listing {
margin-bottom: 100px;
}
Теперь сверстаем карточку товара.
<article class="event-card listing__event-card">
<a href="#" class="event-card__link">
<picture class="event-card__picture">
<img src="images/events/nirvana.jpg" alt="nirvana" class="event-card__img" />
</picture>
<h4 class="event-card__title">Собираемся и слушаем альбом Nirvana</h4>
<p class="event-card__description">
Это третий альбом группы, выпущенный после смерти Курта Кобейна и первый, содержащий студийный материал.
</p>
<time datetime="2022-12-07T18:00" class="event-card__time">
07.12.2022 | начало 18.00
</time>
</a>
</article>
.event-card {
display: flex;
width: 405px;
max-width: 100%;
min-width: 280px;
}
.event-card__link {
display: flex;
flex-direction: column;
width: 100%;
color: #1F1E1E;
text-decoration: none;
}
.event-card__link:hover .event-card__title {
color: #BC3324;
text-decoration-color: #BC3324;
}
.event-card__picture {
display: block;
position: relative;
width: 100%;
margin: 0 0 25px;
font-size: 0;
text-decoration: none;
overflow: hidden;
}
.event-card__picture::after {
content: "";
display: block;
width: 100%;
padding-top: 107.16%;
}
.event-card__img {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.event-card__title {
margin: 0 0 15px;
min-height: 66px;
color: #1F1E1E;
font-weight: 600;
font-size: 24px;
line-height: 33px;
text-decoration: underline transparent;
transition: color 0.2s, text-decoration-color 0.2s;
}
.event-card__description {
margin: 0 0 auto;
font-size: 16px;
line-height: 22px;
}
.event-card__time {
margin: 15px 0 0;
font-weight: 300;
font-size: 14px;
line-height: 19px;
}
Дело осталось за малым: наши карточки довольно крупные, мы можем легко сделать их резиновыми, чтобы они меняли свой размер в зависимости от ширины контейнера. Кроме того нужно задать отступы между карточками. И здесь встаёт вопрос, как мы будем распределять карточки по нашему контейнеру.
В каждом ряду у нас по три карточки. Представим, что список состоит из пяти карточек. Как будут выровнены 4‑я и 5‑я карточки? Как правило, карточки принято выстраивать по левой стороне. Мы можем задать для каждой карточки правый и нижний внешний отступ, а каждой третьей обнулить правый отступ:
.listing__event-card {
margin-right: 60px;
}
.listing__event-card:nth-child(3n) {
margin-right: 0;
}
Но такой подход не очень удобен. Проблема в том, что по адаптиву количество наших карточек в ряду будет изменяться: сначала три, потом две, а в мобильном варианте — одна, и перебивать такие стили очень неудобно.
Проблему можно решить довольно просто: задать всему списку отрицательные внешние отступы, а всем карточкам левый, правый и нижний отступы. Получим следующее:
.listing__events-list {
display: flex;
flex-wrap: wrap;
margin: 0 -30px -120px;
}
.listing__event-card {
width: calc(33.3333% - 60px);
margin: 0 30px 120px;
}
Обратите внимание, в данном случае мы ещё компенсировали нижний отступ последнего ряда карточек отрицательным нижним отступом всего списка.
В данном примере также мы можем увидеть новую для нас возможность — использование CSS-функции calc()
, позволяющей автоматически вычислять размер элементов.
Конечный результат будет выглядеть так:
На что ещё стоит обратить внимание?
- Время мероприятия мы указали с помощью HTML-тега
<time>
и соответствующего атрибутаdatetime
, что позволяет осуществлять автоматическую машинную обработку даты, например — поисковыми системами. - Наш список карточек построен с помощью Flexbox, благодаря чему все карточки в ряду имеют одинаковый размер. Кроме того сама карточка также построена на Flexbox, а текстовому описанию задан нижний автоматический отступ, таким образом дата всегда прибита к нижнему краю карточки вне зависимости от размера текста и заголовка.
- Для заголовка карточки указали минимальную высоту, равную двум строчкам текста. Не самое идеальное решение, но в большинстве случаев позволит выстроить сетку листинга максимально ровной, так как всё, что связывает карточки между собой, — их общая высота в строке flex-контейнера.
- В нескольких местах встречается свойство
transition
, которое задаёт эффект перехода между двумя состояниями элемента. Благодаря этому мы можем плавно менять цвета наших ссылок и кнопок по наведению мыши.
Изображение карточки обёрнуто в тег <picture>
. Для чего он нужен, разберём на одном из следующих уроков. Но обратите внимание на стили:
.event-card__picture {
position: relative;
}
.event-card__picture::after {
content: "";
display: block;
width: 100%;
padding-top: 107.16%;
}
.event-card__img {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
Размер изображению задаёт псевдоэлемент, имеющий жёсткое соотношение сторон благодаря внутреннему вертикальному отступу в процентах, который вычислен делением высоты изображения в макете на его ширину.
Не стоит надеяться, что контент-менеджер всегда будет загружать на сайт изображения в правильных размерах, а бэкенд-разработчик в обязательном порядке настроит автоматическое изменение их размеров или пропорций. Всегда может что-то пойти не так, поэтому стоит предусмотреть заранее всё самим, чтобы наши карточки всегда выглядели аккуратно.
Для этого мы воспользовались CSS-свойством object‑fit
. Возможные значения:
fill
— значение по умолчанию, изображение соответствует заданным размерам, но его пропорции игнорируются.contain
— изображение масштабируется так, чтобы целиком уместиться в заданных размерах, пропорции сохраняются.cover
— изображение масштабируется так, чтобы целиком заполнить заданные размеры, пропорции сохраняются, а всё что не уместилось — обрезается.none
— сохраняются исходные параметры изображения, установленные значения ширины и высоты не влияют на отображение, всё что не уместилось — обрезается.
3. Grid Layout — теория
Колонки и строки
Grid Layout представляет собой двумерную сетку для CSS. Это набор пересекающихся горизонтальных и вертикальных линий, образующих столбцы и строки, в которые могут быть размещены элементы.
Как и Flexbox, Grid Layout состоит из grid-контейнера и grid-элементов.
Задав элементу { display: grid }
или { display: inline‑grid }
, мы создаём grid-контейнер, прямые потомки которого в свою очередь станут grid-элементами.
Давайте сразу рассмотрим живой пример, а затем разберём, что здесь происходит:
.grid {
display: grid;
grid-template-columns: 50px 1fr 2fr;
grid-template-rows: 70px minmax(100px, auto);
grid-auto-rows: auto;
grid-gap: 30px 20px;
}
Мы создали grid-контейнер, и указали ещё несколько CSS-свойств, отвечающих за нашу сетку:
grid-template-columns
— задаёт количество колонок в сетке и может определять ширину каждой из них;grid-template-rows
— определяет параметры строк сетки;grid-gap
— задаёт отступы между строками и столбцами, является сокращением для свойствrow-gap
иcolumn-gap
. Принимает любые единицы длины.
Если с grid‑gap
всё достаточно просто, то на первых двух свойствах стоит остановиться поподробнее.
Для grid‑template‑columns
мы задали три значения, которые определяют для нашей сетки три колонки. Ширина первой колонки задана жёстко — 50px
, а размер других задан в новых для нас единицах — фракциях (fr
), представляющих собой долю имеющегося свободного пространства. Таким образом вторая колонка заняла у нас 1/3 имеющегося места, а третья колонка — 2/3, и мы получили гибкие по ширине блоки.
Для grid‑template‑rows
мы указали два значения, которые соответствуют высоте первой и второй строки соответственно, вместе они представляют собой явную сетку. Если места под контент в элементах этих строк окажется недостаточно, то он выпадет.
Для второй строки мы задали размер с помощью функции: minmax(100px, auto)
. В данном случае минимальная высота строки у нас 100px
, а максимальная будет определяться размером содержимого (auto
). Максимальная высота также может быть жёстко задана в единицах длины.
При этом grid-элементов у нас такое количество, что количество строк оказалось больше, чем мы задали. Это неявная сетка. В таком случае размер ячеек по умолчанию будет определяться содержимым grid-элементов.
Параметры неявной сетки задаются следующими CSS-свойствами:
grid-auto-rows
— задаёт размер неявно созданной строки сетки;grid-auto-columns
— задаёт размер неявно созданной колонки сетки;
Значение по умолчанию для обоих свойств — auto
, что будет соответствовать размеру содержимого.
В нашем пример есть ещё одно неявно заданное свойство { grid‑auto‑flow: row }
.
grid-auto-flow
— CSS-свойство, определяющее, как grid-элементы заполняют сетку.
Возможные значения:
row
— элементы заполняют поочерёдно каждую строку, новые строки добавляются по мере необходимости. Это значение по умолчанию;column
— элементы заполняют поочерёдно каждый столбец, а новые столбцы добавляются по мере необходимости;dense
(row dense
,column dense
) — ключевое слово, указывающее grid-элементами заполнять свободное пространство сетки. Это может привести к нарушению порядка, т.к. элементы будут выстраиваться не в соответствии со своим расположением, а в соответствии с размером.
Добавим в наш пример свойство { grid‑auto‑flow: column }
и посмотрим, что получится:
Мы видим, что наши элементы заполняют не строки, а колонки, создавая новые при необходимости.
В этом же примере мы можем увидеть ещё один способ задания размеров сетки:
grid-template-columns: 35px repeat(2, 50px) repeat(2, 1fr);
Для первой колонки задан размер в 35px
. Далее идёт повтор двух колонок с шириной 50px
и затем две колонки, занимающие по равной фракции.
Но что делать, если мы не знаем, сколько нам нужно колонок, и хотим, чтобы их количество определялось автоматически в зависимости от заданного размера? Для этого есть специальные значения auto-fill
и auto-fit
:
Для первого grid-контейнера здесь задано:
grid-template-columns: repeat(auto-fill, minmax(75px, 1fr));
В этом случае строка заполняется таким количеством колонок, которое возможно с учетом заданной ширины.
Второму grid-контейнеру задано:
grid-template-columns: repeat(auto-fit, minmax(75px, 1fr));
В этом случае поведение будет похожим, но если элементы умещаются в одну строку, они заполнят всё имеющееся пространство.
grid-column и grid-row
Огромный плюс Grid Layout заключается в том, что мы можем не только определить параметры двумерной сетки, но и очень гибко распределять grid-элементы, указав, какие именно ячейки сетки они могут занимать.
Давайте снова посмотрим на пример:
Мы задали следующие стили:
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 20px;
}
.grid__element--1 {
grid-column-start: 2;
grid-column-end: 4;
}
.grid__element--2 {
grid-row-start: 3;
}
.grid__element--3 {
grid-row: 1 / 3;
}
.grid__element--5 {
grid-column: 2 / -1;
}
Для первого элемента .grid__element--1
мы задали:
grid‑column‑start: 2
grid‑column‑end: 4
Это равноценно обобщающему свойству { grid‑column: 2 / 4 }
.
Теперь наш первый grid-элемент начинается с первой колонки, а заканчивается перед четвёртой.
Для свойств grid-row-start
и grid-row-end
аналогично обобщающим является свойство grid-row
.
Так для .grid__element--3
задано:
grid-row: 1 / 3
Третий элемент начинается на первой строке и заканчивается перед третьей.
Пятый элемент .grid__element--5
начинается со второй колонки, а заканчивается — последней, благодаря значению:
grid‑column: 2 / -1
.
Таким образом, мы можем использовать отрицательные значения, указывая последнюю строку или колонку.
Существует также свойство, обобщающее все 4 рассмотренные выше свойства:
.some-grid-element {
grid-area: 1 / 1 / 4 / 2;
}
grid-area
задаёт одновременно:
grid-row-start
,grid‑column‑start
,grid-row-end
grid‑column‑end
.
Впрочем, данное CSS-свойство обладает ещё одной интересной возможностью, о которой речь пойдёт ниже.
Шаблоны grid-областей
Grid Layout имеет ещё один прекрасный и очень удобный способ задавать параметры сетки — именованные области.
Давайте опять разберём на живом примере:
.body {
display: grid;
grid-template-columns: 1fr 100px;
grid-template-rows: 50px 1fr auto;
grid-template-areas:
"header header"
"main aside"
"footer footer";
}
.header {
grid-area: header;
}
.main {
grid-area: main;
}
.aside {
grid-area: aside;
}
.footer {
grid-area: footer;
}
Каждому grid-элементу благодаря свойству grid‑area
мы можем задать собственное имя, а затем с помощью свойства grid‑template‑areas
заполнить нашу сетку данными элементами.
Выравнивание ячеек
Мы можем не только распределять наши grid-элементы по ячейкам, но и выравнивать их как по горизонтали, так и по вертикали. За это отвечают следующие свойства :
justify-items
— выравнивает все grid-элементы по горизонтали, задаётся grid-контейнеру;align-items
— выравнивает все grid-элементы по вертикали, задаётся grid-контейнеру;justify-self
— выравнивает конкретный grid-элемент по горизонтали;align-self
— выравнивает конкретный grid-элемент по вертикали;
Возможные значения:
stretch
— значение по умолчанию. Grid-элемент стремится занять всю ячейку;start
— grid-элементы выравниваются в начале ячейки;end
— grid-элементы выравниваются в конце ячейки;center
— grid-элементы выравниваются в центре ячейки;
Наглядный пример:
4. Grid Layout — вёрстка макета
Поддержка Grid Layout появилась значительно позже Flexbox. Так, например, Flexbox полноценно поддерживается IE11 (правда с некоторыми багами, которые можно обойти), в то время как о Grid Layout для данного браузера можно забыть. Впрочем, если вам требуется поддержка только современных браузеров, об этом можно не переживать и делать всё смело с помощью Grid Layout.
Форма бронирования
На наших макетах присутствует один единственный блок, который невозможно сверстать в соответствии с дизайном и на всём протяжении адаптива без использования Grid Layout: это форма бронирования столика на самом нижнем макете мероприятия.

Мы ещё не ничего не знаем о формах и об адаптиве, поэтому на данный момент сверстаем данный блок «условно» и вернёмся к нему позже.
Сперва рядом с файлом index.html создайте файл event.html, скопировав из первого всю разметку, но оставив <main>
пустым. Далее создадим в нашем мэйне новыe БЭМ-блоки:
<main class="main">
<!-- Здесь будут хлебные крошки -->
<article class="event">
<!-- Здесь пропущенное содержимое страницы -->
<form class="reserve event__reserve">
<div class="reserve__checks">
Чекбоксы
</div>
<div class="reserve__scheme">
Схема столов
</div>
<div class="reserve__order">
Оформление заказа
</div>
<div class="reserve__legend">
Легенда
</div>
</form>
</article>
</main>
.event {
padding: 40px 0 150px;
}
.reserve {
display: grid;
gap: 65px 11%;
grid-template-columns: 405px calc(89% - 405px);
grid-template-areas:
"checks scheme"
"order legend";
width: 100%;
}
.reserve__checks {
grid-area: checks;
}
.reserve__order {
grid-area: order;
}
.reserve__scheme {
grid-area: scheme;
}
.reserve__scheme {
grid-area: scheme;
}
Получим следующий результат:
Резюме
Итак, мы познакомились с Flexbox и Grid Layout и теперь умеем строить любые сетки. Впереди нас ждёт вёрстка всего проекта. Но сперва нужно поговорить об автоматизации и оптимизации процесса разработки, что мы и сделаем на следующем уроке.
5. Материалы для самостоятельного изучения
- Основные понятия Flexbox:
developer.mozilla.org/ru/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox - Основные понятия Grid Layout:
developer.mozilla.org/ru/docs/Web/CSS/CSS_Grid_Layout/Basic_Concepts_of_Grid_Layout - CSS Grid Genetator:
https://grid.layoutit.com/ - Can I Use — сервис для проверки поддерживаемых web-технологий:
https://caniuse.com/