Мой брат недавно отучился на компьютерщика и сейчас завершает стажировку в области фронтенд-разработки. Он узнал и о CSS Grid, и о CSS Flexbox, но в том, как он пользуется этими механизмами создания макетов, я отметил одну особенность, с которой я уже сталкивался. А именно, ему тяжело даётся принятие решений о том, когда использовать Grid, а когда — Flexbox. Например, он использовал CSS Grid для создания макета заголовка сайта. При этом он отметил, что довести проект до ума ему было нелегко, и что ему пришлось долго экспериментировать с grid-column
и настраивать всё до тех пор, пока у него не получилось то, что ему было нужно.
Честно говоря, мне это не понравилось. Поэтому я решил поискать какой-нибудь ресурс, который помог бы моему брату как следует уяснить различия между Grid и Flexbox и дал бы возможность взглянуть на примеры, созданные с использованием обеих этих технологий. Но ничего подходящего мне найти не удалось. Тогда я решил написать статью, посвящённую Grid и Flexbox. Надеюсь, она получилась понятной.
Введение
Прежде чем обсуждать концепции и рассматривать примеры, мне хотелось бы удостовериться в том, что вам понятна разница между CSS Grid и CSS Flexbox. Grid — это модуль для создания табличных, двумерных макетов, позволяющих размещать элементы в строках и столбцах макета. Flexbox позволяет размещать элементы только по строкам или только по столбцам, то есть — в одном измерении.
Если вы совершенно не знакомы с технологиями Grid и Flexbox — взгляните на эту мою статью. Если вы с ними знакомы — это замечательно, это значит, что мы можем перейти к исследованию их различий и к разговору о том, кода и почему каждую из них стоит использовать.
Разница между Grid и Flexbox
Давайте сразу проясним одну вещь: не существует способа однозначно сделать выбор между Grid и Flexbox. Более того, нет правильного и неправильного способа их использования. Данный материал — это своего рода руководство, в котором будут даны рекомендации по использованию того или иного механизма построения макета для конкретных случаев. Я объясню общие концепции и затем перейду к примерам. Вам же нужно будет принимать решения самостоятельно и самостоятельно проводить дальнейшие эксперименты.
/* Flexbox-контейнер*/
.wrapper {
display: flex;
}
/* Grid-контейнер*/
.wrapper {
display: grid;
grid-template-columns: 2fr 1fr;
grid-gap: 16px;
}
Flexbox — одномерный макет. Grid — многомерный макет
Обратили внимание на одну особенность? Flexbox «раскладывает» список элементов в одном измерении, а Grid размещает их в ячейках сетки, состоящей из строк и столбцов. В данном примере во Flexbox-макете элементы «разложены» горизонтально, в строке воображаемой таблицы, но их, если нужно, можно расположить и в столбце такой таблицы, вертикально.
/* Flexbox-контейнер*/
.wrapper {
display: flex;
flex-direction: column;
}
Элементы во Flexbox-макете расположены вертикально
Что лучше выбрать?
Выбор между Grid и Flexbox может оказаться немного сложным (иногда), особенно если вы — новичок в CSS. Я вас понимаю! Поэтому — вот несколько вопросов, которые помогают мне сдвинуться с мёртвой точки в ситуациях, когда приходится выбирать между Grid и Flexbox:
- Как должны быть расположены дочерние элементы компонента-контейнера? Должны ли они быть размещены в одной строке или столбце, или по строкам и столбцам некоей сетки?
- Как должны вести себя компоненты на экранах разных размеров?
Чаще всего, если компонент, с которым вы работаете, содержит другие компоненты, расположенные в виде строки или столбца, то, вероятнее всего, лучше всего будет выбрать Flexbox. Рассмотрим следующий пример.
Компонент и два его дочерних элемента, расположенных в одной строке
Но если при работе над компонентом становится понятным, что его дочерние элементы должны располагаться в чём-то вроде таблицы, тогда лучше выбрать Grid.
Макет, для описания которого лучше подойдёт Grid
Теперь, когда я объяснил основные различия Grid и Flexbox, предлагаю перейти к примерам и рассмотреть применение вышеописанных концепций на практике.
Сценарии использования и практические примеры: Grid
▍Боковая панель и основная область страницы
Если в дизайне страницы, над макетом которой вы работаете, можно выделить боковую панель и основную область, это значит, что вам лучше всего обратить внимание на Grid. Взглянем на следующую схему.
Боковая панель и основная область страницы
Вот как я описал бы такой макет средствами HTML и CSS.
HTML-разметка:
<div class="wrapper">
<aside>Sidebar</aside>
<main>Main</main>
</div>
CSS-код:
@media (min-width: 800px) {
.wrapper {
display: grid;
grid-template-columns: 200px 1fr;
grid-gap: 16px;
}
aside {
align-self: start;
}
}
Если бы к элементу <aside>
не было бы применено свойство align-self: start
, его высота всегда равнялась бы высоте элемента <main>
и не зависела бы от содержимого <aside>
.
▍Набор карточек
Как уже было сказано в начале этого материала, CSS Grid совершенно естественным образом подходит для разработки сеточных макетов. В результате оказывается, что Grid отлично показывает себя при создании макетов, предназначенных для вывода набора карточек в виде таблицы.
Набор карточек
Вот как я описал бы такой макет средствами CSS:
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 16px;
}
Ширина столбца сетки будет равна, как минимум, 200px
, а, если места на экране не хватит, карточки будут перенесены на новую строку. Стоит сказать о том, что использование такого макета может привести к необходимости горизонтальной прокрутки страницы в том случае, если ширина области просмотра будет меньше, чем 200px
.
Для того чтобы решить эту проблему, можно использовать Grid-макет только в тех случаях, когда ширина области просмотра достаточно велика для его нормального вывода. Сделать это можно так:
@media (min-width: 800px) {
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 16px;
}
}
▍Макет раздела сайта, имеющего сложную структуру
Для того чтобы спроектировать макет страницы, показанной ниже, Grid можно использовать дважды. Первое применение Grid позволяет разбить страницу на две области (это — боковая панель с текстом Contact us
и основная область страницы с формой). Второе применение Grid заключается в разметке области, в которой расположены элементы страницы.
Страница, для формирования которой Grid можно использовать дважды
CSS Grid просто идеально подходит для формирования таких макетов. Вот как выглядит CSS-код подобного решения:
@media (min-width: 800px) {
.wrapper {
display: grid;
grid-template-columns: 200px 1fr;
}
.form-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 16px;
}
.form-message,
.form-button {
grid-column: 1 / 3; /* пусть займут всё свободное пространство по ширине */
}
}
Этот пример я позаимствовал из собственной статьи, посвящённой созданию макетов форм с использованием CSS Grid.
Сценарии использования и практические примеры: Flexbox
▍Навигационная панель
В 90% случаев при разработке навигационных панелей веб-сайтов стоит пользоваться CSS Flexbox. Чаще всего подобные панели устроены так: слева находится логотип, справа — навигационные элементы. Для того чтобы распределить элементы подобным образом Flexbox подходит идеально.
Навигационная панель
Для того чтобы описать средствами CSS вышеприведённый макет, достаточно сделать следующее:
.site-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
Та же идея применима и для описания макета, показанного на следующем рисунке.
Навигационная панель
Обратите внимание на то, что структура этой навигационной панели немного отличается от структуры той, что была рассмотрена выше. Но и здесь расстояние между элементами настраивается с помощью свойства justify-content
.
▍Список действий
Когда вам встречается слово «список», то первое, что приходит в голову, наверняка, выглядит как набор элементов, расположенных вертикально. Но элементы списка могут размещаться не только по вертикали, но и по горизонтали. Прошу это учитывать.
Панели, реализующие списки действий, можно найти на сайтах Facebook или Twitter. Эти списки состоят из кнопок, пользователь может выполнять некие действия, щёлкая по этим кнопкам.
Списки действий
Как видите, элементы этих списков распределены по горизонтали, в одной строке. Для реализации макета подобных списков отлично подходит Flexbox. И это — один из основных сценариев использования Flexbox.
.actions-list {
display: flex;
}
.actions-list__item {
flex: 1; /* разместить элементы так, чтобы свободное пространство между ними было бы распределено равномерно */
}
Ещё один вариант использования такого подхода может быть представлен кнопками, находящимися в заголовке или в нижней части модального окна.
Модальное окно
И у заголовка, и у «подвала» окна есть дочерние элементы, которые выводятся в одной строке. Ниже показана методика настройки расстояния между ними.
Вот стили заголовочной части окна:
.modal-header {
display: flex;
justify-content: space-between;
}
А вот «подвал» стилизуется немного иначе. Кнопка Cancel
использует автоматическую настройку внешних отступов для того чтобы сместиться вправо. Вот моя статья, посвящённая ключевому слову auto
в CSS.
.cancel__action {
margin-left: auto;
}
Возможно, название класса, .cancel__action
, выбрано не очень удачно, но тут я не хочу затрагивать тему использования соглашений об именовании CSS-сущностей.
▍Элементы форм
Комбинация из поля ввода и кнопки, расположенной сразу за ним, даёт нам отличную возможность к применению Flexbox. Взглянем на следующий рисунок.
Элементы форм
Поле ввода первой формы занимает свободное пространство, оставшееся в форме после размещения кнопки. То есть перед нами пример формы, ширина которой может настраиваться динамически. То же самое справедливо и в отношении второй формы (она взята из мессенджера Facebook). При изменении её ширины излишки места занимает поле ввода. Присмотримся к этой форме.
Изменение ширины поля при изменении ширины формы
Вот CSS-код:
.input { flex: 1 1 auto; }
Обратите внимание на то, что если бы при настройке поля не использовалась бы конструкция flex: 1 1 auto;
, то поле не смогло бы растягиваться, занимая излишки пространства.
▍Дискуссии и комментарии
Ещё одна ситуация, в которой обычно используют Flexbox, представлена всяческими дискуссиями, состоящими из множества сообщений. Ниже показан пример подобного сообщения.
Сообщение
В элементе, выводящем сообщение, имеется фото пользователя и комментарий. Комментарий занимает, по ширине, всё доступное ему свободное пространство родительского элемента. Это идеальный вариант использования Flexbox.
▍Компоненты-карточки
Компоненты-карточки могут быть очень разными, но обычно они выглядят примерно так, как показано ниже.
Разные варианты компонента-карточки
Слева показана карточка, дочерние элементы которой расположены друг над другом. При создании Flex-макета такой карточки использовано значение column
свойства flex-direction
. Справа элементы карточки распределены по вертикали, здесь свойство flex-direction
установлено в значение row
. Тут стоит учитывать то, что значением этого свойства, которое оно принимает по умолчанию, является именно row
.
Вот CSS-код:
.card {
display: flex;
flex-direction: column;
}
@media (min-width: 800px) {
.card {
flex-direction: row;
}
}
Ещё один распространённый вариант карточки предусматривает наличие на ней значка, ниже которого расположен текст. Это может быть кнопка, ссылка, или просто элемент оформления страницы. Рассмотрим два варианта таких карточек.
Два варианта карточек
Обратите внимание на то, что иконка и текстовая метка центрированы по горизонтали и по вертикали. Благодаря Flexbox сделать это очень легко.
.card {
display: flex;
flex-direction: column;
align-items: center;
}
Для создания стиля карточки, элементы которой расположены по вертикали, нужно просто убрать свойство flex-direction: column
, что приведёт к тому, что оно примет значение, задаваемое ему по умолчанию (то есть — row
).
▍Меню в виде вкладок
Если говорить об элементах, занимающих всю ширину экрана, и при этом содержащих дочерние элементы, которые должны занимать всё доступное им свободное пространство, можно отметить, что Flexbox идеально подходит для создания таких элементов.
Меню с вкладками
В вышеприведённом примере каждый элемент должен заполнять доступное ему пространство, при этом все элементы должны иметь одну и ту же ширину. Если свойство display
элемента-контейнера установить в значение flex
, добиться этого совсем не сложно:
.tabs__item {
flex-grow: 1;
}
Этот подход используется в React Native для создания меню мобильных приложений. Вот пример кода, который формирует меню, напоминающее то, что мы только что рассмотрели. Этот код взят отсюда.
import React from 'react';
import { View } from 'react-native';
export default FlexDirectionBasics = () => {
return (
<View style=>
<View style= />
<View style= />
<View style= />
</View>
);
};
▍Списки возможностей чего-либо
Во Flexbox мне больше всего нравится то, что этот механизм формирования макетов позволяет обращать направление следования элементов. По умолчанию элементы идут слева направо, стандартным значением свойства flex-direction
является row
. Но элементы можно вывести и в обратном порядке:
.item {
flex-direction: row-reverse;
}
Обратите внимание на то, что во втором элементе следующего списка его дочерние элементы расположены справа налево. Делается это именно так, как показано выше.
Изменение порядка вывода дочерних элементов flex-контейнера.
Иногда это может оказаться очень кстати.
▍Центрирование содержимого раздела
Представим, что у нас имеется раздел страницы, выводимый в её верхней части. Нам нужно центрировать его содержимое по вертикали и по горизонтали. Горизонтальное центрирование — это очень просто, так как оно реализуется с помощью механизмов выравнивания текста.
Раздел страницы, содержимое которого надо центрировать
.hero {
text-align: center;
}
А как центрировать содержимое по вертикали? Как нам в этом может помочь Flexbox? А вот как:
.hero {
display: flex;
flex-direction: column;
align-items: center; /* центрирует элементы по горизонтали */
justify-content: center; /* центрирует элементы по вертикали */
text-align: center;
}
Комбинирование Grid и Flexbox
Существуют сценарии, в соответствии с которыми Grid и Flexbox применяются самостоятельно. Но в некоторых случаях эти средства для разработки макетов есть смысл использовать совместно. Когда я размышляю о комбинировании Grid и Flexbox, первое, что приходит в голову, это список карточек. Grid используется для размещения карточек на странице, а Flexbox — для создания самих карточек.
Карточки на странице
К этому макету предъявлены следующие требования:
- Карточки, расположенные в разных строках сетки, должны иметь одинаковую высоту.
- Ссылка
Read more
должна быть расположена в самой нижней части карточки. Её положение не должно зависеть от высоты карточки. - При построении Grid-макета нужно использовать CSS-функцию
minmax()
.
Рассмотрим HTML-разметку:
<div class="wrapper">
<article class="card">
<img src="sunrise.jpg" alt="">
<div class="card__content">
<h2><font color="#3AC1EF"><!-- Title --></font></h2>
<p><!-- Desc --></p>
<p class="card_link"><a href="#">Read more</a></p>
</div>
</article>
</div>
Вот CSS-код:
@media (min-width: 500px) {
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 16px;
}
}
.card {
display: flex; /* [1] */
flex-direction: column; /* [2] */
}
.card__content {
flex-grow: 1; /* [3] */
display: flex; /* [4] */
flex-direction: column;
}
.card__link {
margin-top: auto; /* [5] */
}
Позвольте мне пояснить этот код (номера пунктов соответствуют номерам, указанным в комментариях):
- Элемент
.card
играет роль Flexbox-контейнера. - Направление расположения элементов установлено в значение
column
. Это значит, что элементы карточек будет расположены друг над другом. - Позволим содержимому карточек, описываемому элементом
.card__content
, занимать доступное пространство. - Превратим элемент
.card__content
во Flex-контейнер. - И наконец, воспользуемся свойством
margin-top: auto
элемента.card__link
для того чтобы поместить ссылку в самый низ карточки, сделав так, чтобы её положение не зависело бы от высоты карточки.
Как видите, совместное использование Grid и Flexbox — это не так уж и сложно. Эти два инструмента способны дать нам множество способов создания макетов веб-страниц. Предлагаю использовать их правильно и комбинировать их только в том случае, если это, как в вышеприведённом примере, действительно необходимо.
Запасные варианты и поддержка старых браузеров
▍Использование supports
Пару месяцев назад мне отправили твит, в котором говорилось, что в IE11 мой сайт не может нормально работать. После того, как я это проверил, я заметил, что сайт, и правда, ведёт себя в IE11 очень странно. Всё содержимое сайта съехало в его левую верхнюю область. Работать с сайтом стало совершенно невозможно.
Проблема с сайтом в IE11
Да, это мой сайт, сайт фронтенд-разработчика, открытый в IE11. Поначалу я почувствовал себя сбитым с толку. Почему это происходит? Тогда я вспомнил, что CSS Grid поддерживается в IE11, но речь идёт о старой версии технологии, выпущенной Microsoft. Решение этой проблемы оказалось очень простым. Оно заключалось в использовании директивы @supports
, направленной на то, чтобы технология CSS Grid использовалась бы только в новых браузерах.
@supports (grid-area: auto) {
body {
display: grid;
}
}
Поясню этот код. Я воспользовался здесь проверкой свойства grid-area
из-за того, что оно существует только в новой спецификации, которая вышла в марте 2017 и продолжает оставаться актуальной. Так как IE не поддерживает использование директивы @supports
, он проигнорирует всё это правило. В результате современный CSS Grid-макет будет использоваться только в тех браузерах, которые его поддерживают.
▍Использование Flexbox-макета как запасного варианта, применяемого вместо Grid-макета
Если Flexbox не подходит для формирования сеточных Grid-макетов, это ещё не значит, что данная технология не может играть роль запасного варианта, применяемого вместо таких макетов. Flex можно использовать как замену Grid в тех браузерах, которые не поддерживают Grid. Вот созданный мной инструмент, который предназначен для преобразования Grid-макетов во Flex-макеты.
@mixin grid() {
display: flex;
flex-wrap: wrap;
@supports (grid-area: auto) {
display: grid;
grid-gap: 16px 16px;
}
}
@mixin gridAuto() {
margin-left: -16px;
> * {
margin-bottom: 16px;
margin-left: 16px;
}
@media (min-width: 320px) {
> * {
width: calc((99% / #{2}) - 16px);
flex: 0 0 calc((99% / #{2}) - 16px);
}
}
@media (min-width: 768px) {
> * {
width: calc((99% / #{3}) - 16px);
flex: 0 0 calc((99% / #{3}) - 16px);
}
}
@supports (grid-area: auto) {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
margin-left: 0;
> * {
width: auto;
margin-left: 0;
margin-bottom: 0;
}
}
}
Вот как работает этот код:
- К элементу-контейнеру добавляются свойства
display: flex
иflex-wrap: wrap
. - Проверяется поддержка CSS Grid. Если эта технология поддерживается, то вместо свойства
display: flex
будет использовано свойствоdisplay: grid
. - С помощью селектора
> *
мы можем выбирать прямых потомков элемента-контейнера. После их выбора мы можем настраивать их размеры. - Конечно, между ними должны быть отступы, которые, если поддерживается CSS Grid, будут заменены на
grid-gap
.
Пользоваться SASS-миксинами можно так:
.wrapper {
@include grid();
@include gridAuto();
}
Вот пример на CodePen.
Проблемы, возникающие при использовании Grid и Flexbox
Когда я делал код-ревью для проектов брата, я заметил несколько случаев некорректного использования Grid и Flexbox. Полагаю, нелишним будет рассказать здесь о некоторых из таких случаев.
▍Использование Grid для заголовочной части сайта
Эта ошибка была одной из тех «последних капель», которые подвигли меня на написание данной статьи. А именно, я заметил, что брат использует при создании заголовочной части сайта Grid.
Он, когда об этом рассказывал, говорил, что это было сложно, и о том, что не может толком разобраться в CSS Grid. Мысль о том, что с CSS Grid сложно работать, появилась у него после того, как он использовал при разработке макета неподходящую технологию. С Grid работать несложно. А проблема тут лишь в том, что при разработке макета был использован неподходящий для этого механизм.
Рассмотрим следующий пример, иллюстрирующий то, с чем я столкнулся, анализируя неудачный макет.
Неудачный макет
Вот CSS-код:
.site-header {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
}
.site-nav {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
Здесь механизмы CSS Grid были использованы дважды. Первый раз — для формирования всей заголовочной части сайта, а второй раз — для настройки размещения навигационных элементов. Для точной настройки расстояния между элементами было использовано свойство grid-template-columns
. В том коде, что я видел, были и другие странности, я их уже и не вспомню, но надеюсь, что мне удалось донести до вас самое главное.
▍Использование Grid для панелей с вкладками
Ещё один пример неправильного использования Grid представляет собой компонент с вкладками, макет которого создан с применением этой технологии. Взгляните на следующую схему.
Неправильный подход к созданию макета компонента с вкладками
Вот CSS-код такого макета:
.tabs-wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
Глядя на этот код, я вижу, что разработчик исходил из предположения о том, что вкладок у компонента будет всего три. В результате он и воспользовался для настройки столбцов конструкцией 1fr 1fr 1fr
. Если количество столбцов изменится — макет, что называется, «поломается».
▍Чрезмерное использование Flexbox или Grid
Помните о том, что методы формирования макетов, существующие в CSS уже очень давно, могут оказаться идеально подходящими для решения некоторых задач. Неоправданное использование Flexbox или Grid может, со временем, увеличить сложность вашего CSS-кода. Я не имею в виду то, что это — технологии, которыми сложно пользоваться. Я говорю о том, что гораздо лучше пользоваться ими правильно, применяя их в тех ситуациях, в которых они лучше всего себя показывают.
Например, имеется заголовочный раздел сайта, содержимое которого нужно центрировать по горизонтали.
Содержимое, которое центрировано по горизонтали
Может, тут нужно воспользоваться Flexbox? А что если добиться этого с помощью text-align: center
? Зачем использовать Flexbox в ситуации, кода стоящую перед разработчиком задачу можно решить с помощью более простых средств?
В каких ситуациях, проектируя макеты веб-страниц, вы используете CSS Grid, а в каких — CSS Flexbox?
Автор: ru_vds