Модули CSS раскладки — что такое и как готовить
В 20-ом веке браузеры были гораздо менее развиты, а CSS сильно ограничен. Он подходил только для оформления простых текстов что-то вроде документов Word. Для оформления сайтов приходилось обращаться к таблицам, чтобы создавать подобие типографской сетки для раскладки элементов. Пока в 2003 году не появился CSS Zen Garden, пропагандирующий оформление сайтов с помощью CSS. В оформлении использовались хаки с флоатами и другие трюки, но технология была несовершенна. Далее, с развитием браузеров, появились инлайн-блоки. Они неплохо справлялись с расположением элементов в ряд, но тоже имели недостатки. И только в начале десятых появились новые модули раскладки: Flexbox и Grid Layout. О них и поговорим.
Меня зовут Лев Солнцев. Я работал в Badoo, Яндекс, МойОфис, Тинькофф, X5 Digital. Написал несколько статей для сайта web-standards.ru. Пару раз выступал с докладами на разных мероприятиях, а также мейнтейнерил SVGO в течение пяти лет. В общем, есть опыт, которым хочется поделиться. Давайте посмотрим на плюсы и минусы Flexbox и Grid Layout и поговорим о том, как и в каких случаях их использовать.
Дисклеймер: эта статья создана на основе расшифровки моего доклада с MoscowCSS 21.
Flexbox
Flexbox появились из технологии браузера Firefox, с помощью которой оформлялись его тулбары. Они отлично работают для расположения элементов в ряд. Причём, ряд может быть как горизонтальным, так и вертикальным. А направление задаётся в любую сторону: вправо, влево, вниз или вверх. За это отвечает свойство flex-direction.
Кроме того, элементы можно переносить на другой ряд. За это отвечает свойство flex-wrap. Таким образом, создаются не только простые ряды элементов, но и чуть более сложные варианты раскладки страниц.
Одним из новых свойств Flexbox стало то, что элементы растягиваются или сжимаются в зависимости от того, есть ли лишнее место, которое следует распределить, или, наоборот, нужно уместить элементы в ограниченном пространстве.
Flex-свойства
Свойство flex-grow распределяет среди элементов незанятое пространство пропорционально своему значению.
Как можно видеть на слайде, при flex-grow: 1 — каждый пункт стал длиннее на одно и то же значение. При flex-grow: 2 — элемент получил в два раза больше места, чем остальные со значением, равным 1.
Сокращённая запись flex
flex: 0 1 auto;
— значение по умолчанию, соответствует правилам:
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
Существует сокращённая запись flex, которая позволяет записать все свойства элемента flex-grow, flex-shrink и flex-basic. По умолчанию flex имеет значение 0 1 auto. Оно означает, что элемент не растягивается, но при необходимости может сжиматься, когда не хватает места.
flex: none = flex: 0 0 auto
flex-grow: 0;
flex-shrink: 0;
flex-basis: auto;
Есть предопределённые полезные значения. Одно из них — none. Оно расшифровывается как 0 0 auto и означает, что элемент зависит от своего размера, не растягивается и не сжимается.
Другое значение — auto. Оно означает, что элемент обладает полной гибкостью, отталкиваясь от своего размера.
Как и в любой сокращённой записи, можно указать частичное значение. Тогда незаданные части автоматически дополнятся до значений: 1 1 и 0. Если указать одно число, то оно будет установлено для flex-grow. Если указать единицы длины, то оно установится для значения flex-basis. Интересно, что можно указать значение 0 пикселей, и это будет не то же самое, что просто 0 без единицы измерения. Это тот случай, когда нельзя опускать единицы длины, так как разные способы записи имеют разные смыслы. Однако это не имеет никакого практического значения и важно разве что для парсеров и работающих с CSS инструментов.
Поведение по умолчанию
Напомню про значение по умолчанию: элементы не растягиваются, но могут сжиматься. Вот пример реального бага, который был у нас на сайте:
В боковой панели с фильтрами появилась полоса прокрутки. Названия категорий немного не поместились, и браузер решил позаимствовать место у чекбоксов. Это произошло именно потому, что у элемента было значение по умолчанию. На этапе разработки мы ловили ещё несколько похожих случаев, которые не доезжали до продакшена. Поэтому для таких элементов стоит устанавливать значения flex: none или flex-shrink: 0, чтобы подобных вещей не происходило.
.checkbox {
flex: none;
/* или */
flex-shrink: 0;
}
Мы рассмотрели свойства flex-row и flex-shrink, но у нас осталось свойство flex-basis. Оно устанавливает базовый размер элемента width или height в зависимости от направления flex-direction. По умолчанию оно имеет значение auto. Его размер зависит от содержимого, учитывая заданное соотношение сторон, собственные размеры (как у картинок) и другие детали. То есть определяется автоматически. Это значение можно поменять, если это требуется для целей вашей раскладки. Например, задать процентное значение от ширины.
Примеры
Давайте посмотрим на примере:
У нас есть раскладка из 3-х элементов: поля ввода имени, поля для адреса подписной почты и кнопки отправки. Все три элемента тянутся, но у поля для адреса подписной почты базовый размер в три раза больше, чем у остальных. Если мы начнём тянуть элементы, то увидим, что они ужимаются до минимального размера, пока кнопка не перенесётся на следующий ряд. Потом то же самое происходит с элементом подписной почты. И, наконец, когда ни один элемент не помещается в один ряд, они становятся каждый на свой ряд.
form {
display: flex;
flex-wrap: wrap;
}
Источник: 4 CSS Layouts to Build Responsive Websites Without Media Queries
input, button {
flex: 1 1 10ch;
}
input[type="email"] {
flex: 3 1 30ch;
}
Таким образом, можно делать адаптивную раскладку даже без применения медиа выражений.
Есть ещё один пример. Мы хотим сделать так, чтобы элемент в колонке не только переносился, но и растягивался в случае переноса.
Если сделать так: flex-grow: 1;
для первой колонки и
flex-grow: 1;
flex-basis: 300px;
для второй, то оба элемента в одном ряду будут растягиваться:
Если же мы хотим, чтобы дополнительный элемент растягивался только в отдельном ряду, есть такой хак:
flex-grow: 9999;
Колонка с этим значением становится настолько широкой, что дополнительный элемент в ней практически не тянется.
Плюсы и минусы
Мы рассмотрели Flexbox и можем выделить его отличительные характеристики. С его помощью можно:
создать новую область раскладки;
расположить элементы в ряд по горизонтали или вертикали;
расположить элементы как угодно: в начале, в конце, посередине;
выровнять элементы по высоте или ширине. Например, сделать одинаковую высоту;
элементы могут растягиваться и сжиматься. В частности, можно задать высоту в зависимости от других элементов;
элементы могут переноситься по строкам и компоноваться в сложные структуры, подобно таблицам.
Ключевые свойства Flexbox заключаются в том, что можно задать высоту элемента в зависимости от других элементов. Ранее это было практически невозможно, особенно если мы не знали их высоту. А ещё с помощью специальных свойств можно выравнивать элементы, что ранее также было недоступно.
С помощью Flexbox можно делать более сложные конструкции для дизайна всей страницы.
Минусы Flexbox вытекают из плюсов. Например, если требуется сложная сетка, нужно создавать из элементов Flexbox сложную конструкцию, похожую на таблицу по рядам и ячейкам. При этом элементам (колонкам) надо чётко задавать ширину, иначе в последнем ряду не получится сделать, чтобы элементы имели ту же ширину, что и в предыдущих, когда их количество меньше. С этой задачей хорошо справляется Grid Layout.
Grid Layout
Grid Layout придумала компания Microsoft. В 2011 году они делали плиточную раскладку для телефонов на Windows Phone и решили задавать раскладку, определяя параметры сетки и ее размеры. Это вроде бы простая вещь оказалась отличным инструментом. Тогда же появилась новая единица измерения fr (fraction). Она обозначает долю места, которое доступно для распределения в grid-контейнере.
Grid элементы
display: grid;
grid-template-rows: 100px 100px 100px;
grid-template-columns: 1fr 1fr 1fr;
Можно задавать разные параметры сетки. Например, три колонки равной ширины:
grid-template-columns: 1fr 1fr 1fr;
Две колонки, первая колонка в два раза шире второй:
grid-template-columns: 2fr 1fr;
Три колонки, где последняя занимает всю оставшуюся ширину:
grid-template-columns: 100px min-content 1fr;
С помощью свойств grid-column-start, grid-row-end, grid-column-start и grid-column-end мы можем указать положение элемента в сетке, указывая соответствующие grid-линии.
grid-row-start: 2;
grid-row-end: 3;
grid-column-start: 1;
grid-column-end: 3;
Также есть сокращенные свойства, значения в которых указываются через слэш:
grid-row: 2 / 3;
grid-column: 1 / 3;
Можно указать положение с конца сетки с помощью отрицательных значений.
Grid свойства
Их можно перемешивать. Например, указать что элемент занимает положение с первой колонки до предпоследней. Положительные числа идут с начала (1 — первая), отрицательные — с конца (−1 — последняя). С первой до последней: 1 / −1. До предпоследней »/ −2». И так далее.
grid-row-start: -3;
grid-row-end: -2;
grid-column-start: -4;
grid-column-end: -2;
grid-column: -4 / -2;
grid-column: 1 / -2;
Так можно указать, что элемент занимает несколько ячеек. В HTML это можно было сделать в таблице атрибутами colspan и rowspan. В CSS, даже с помощью других модулей, до появления Grid Layout этого способа не было. Можно указать элементу, что он занимает несколько ячеек с помощью ключевого слова span.
grid-column-end: span 2;
grid-column: 1 / span 2;
Для удобства линиям можно задавать имена через квадратные скобки. Например, указывать несколько имён и использовать их с классом в HTML.
display: grid;
grid-template-rows:
[header-start] 100px
[header-end content-start] 100px
[content-end footer-start] 100px [footer-end];
grid-template-columns:
[start] 100px [middle1] 100px [middle2] 100px [end];
Но самое, так сказать, мощное средство — это возможность задать раскладку через имена областей.
У каждой строки должно быть одинаковое количество элементов. А каждая объявленная область должна иметь прямоугольную форму (a a b, c c b, e e e), непрямоугольные области, такие как в тетрисе (a a b, c c e, c e e) работать не будут:
grid-template-areas:
"logo header header"
"content content aside"
"footer footer footer";
Отдельным элементам с помощью свойства grid-area мы указываем, в каком месте он должен быть:
grid-area: content;
Эти области также создают имена grid-линий с помощью суффиксов start и end.
grid-column: content-start / content-end;
grid-row: content-start / content-end;
Ещё свойство grid-area является сокращённой записью для grid-row и grid-column, указывая номера линий через слэш, подобно координатам. Сначала первый ряд и первая колонка, затем конечные ряд и колонка.
grid-area: 2 / 1 / 3 / 3;
grid-area:
Шаблоны можно объединить с указанием размеров и имён в одном свойстве. Получатся вот такие сокращённые свойства:
grid-template:
"logo header header" 100px
"content content aside" 100px
"footer footer footer" 100px
/ 1fr 1fr 1fr;
После строк мы указываем высоту ряда, а после слэша размеры колонок.
Чтобы не создавать лишние grid-линии, появились свойства gap, которые позволяют задавать промежутки между ячейками и рядами.
grid-row-gap: 20px;
grid-column-gap: 10px;
— то же, что и
row-gap: 20px;
column-gap: 10px;
или
gap: 20px 10px;
Заполнение сетки
По умолчанию grid заполняется слева направо и сверху вниз. А если сетка не задана, то создаются новые ряды. За это отвечает свойство grid-auto-flow.
display: grid;
grid-template-rows: none;
grid-template-columns: none;
grid-auto-flow: row;
Если элемент занимает несколько ячеек, то grid могут создавать пропуски.
Элементы располагаются друг за другом, и если один из них не помещается и занимает больше, чем доступно, он переносится. Этого можно избежать с помощью свойства grid-auto-flow. Его значение dense говорит браузеру, чтобы он использовал плотную упаковку, чтобы не оставалось пропусков.
.container {
grid-auto-flow: dense
}
:nth-child(3) {
grid-column: span 2;
}
Но это может привести к тому, что элементы окажутся не на том месте, где ожидалось. А ещё немного нагрузить браузер с расчётами. Так что надо быть осторожным с его применением.
Ещё с помощью свойства grid-auto-flow: column можно переключить направление заполнения сетки. Тогда элементы будут идти сверху вниз и слева направо, а если сетка не задана, то будут создаваться новые колонки.
display: grid;
grid-auto-flow: column;
В этом плане свойство grid-auto-flow: column похоже на flex-direction: column, но имеет другой смысл. Если flex-direction: column говорит, что Flexbox идёт вертикально в колонку, то при grid-auto-flow: column создаются новые колонки в горизонтальном направлении. Это немного взрывает мозг, но важно помнить разницу.
Задать размеры автоматически создаваемых колонок можно с помощью свойства grid-template-columns.
Если значений меньше, чем колонок, то значения повторяются.
display: grid;
Grid-template-columns: repeat(3, minmax(100px, 1fr));
Чтобы много раз не повторять одно те же значение, есть функция repeat (), в которой задаётся количество повторений и значение, которое нужно повторить. Оно может быть одно, или их может быть несколько. Это особенно полезно для таких громоздких значений, как minmax (), которое позволяет указать минимальное и максимальное значение размера.
Ещё можно не указывать количество повторений, а использовать свойство auto-fill.
repeat(auto-fill, minmax(100px, 1fr));
Тогда будет создано столько колонок, сколько умещается на экране.
Примеры
Как только места для колонок не хватает, создаётся новая.
Как можно заметить, новые колонки создаются, даже если элементов нет. Если это не то поведение, которое вы хотите, есть похожее свойство, которое называется auto-fit. Оно делает то же самое, что и auto-fill, но если элементов нет, то лишних колонок не создается. Если быть точнее, то в спецификации сказано, что они схлопываются вместо с промежутками, как будто у них размер 0 пикселей.
Какой способ выбрать, зависит от ваших нужд. Нужно ли вам, например, чтобы одна колонка расширялась как на меме ниже или нет.
Сокращённая запись grid
Существует сокращённое свойство grid. Оно позволяет создать шаблон раскладки, как grid-template, либо указать автоматическое заполнение с помощью ключевого слова auto-flow по рядам или по колонкам через слэш.
grid: <'grid-template'> | <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
Вот пример сокращённого свойства grid как grid-template:
display: grid;
grid: "logo header header" 100px
"content content aside" 100px
"footer footer footer" 100px
/ 1fr 1fr 1fr;
Однако надо помнить, что как и любое сокращенное свойство, grid переопределяет все значения, включая значение автоматических колонок, то есть все свойства серии grid-auto-*.
Например, можно в одну строку создать колонку, где каждый ряд имеет ширину 100 пикселей.
display: grid;
grid: auto-flow 1fr / 100px;
То же самое можно сделать для горизонтального ряда высотой 100 пикселей с автоматически создаваемыми колонками. Это позволяет очень просто задавать правила поведения.
grid: 100px / auto-flow 1fr;
Плюсы и минусы
Мы рассмотрели Grid Layout и можем подвести итог:
он создаёт модульную сетку для раскладки, которая занимает всю область страницы. Элементы можно расположить по сетке произвольным образом. Это уникальное свойство, которого раньше не было в других методах. В том же Flexbox каждый элемент находится в своём ряду;
с помощью grid элементы можно переставлять каким угодно образом. Например, создавать абсолютно разные раскладки для мобильных экранов и компьютеров;
также мы можем очень мощно и гибко задавать, по каким правилам строится сетка. Вплоть до автоматического создания;
по умолчанию элементы растягиваются по своим ячейкам, если у них не задано соотношение сторон. К таким элементам относятся картинки и видео.
Теперь давайте сравним особенности работы Flexbox и Grid Layout.
Сравнение Flexbox и Grid Layout
Есть популярная задача: задать колонки равной ширины. Может показаться, что для этого подойдёт flex: auto. Но Flexbox автоматически распределяет оставшееся место. Если у элемента разный изначальный размер, то итоговый размер тоже будет разный. Хотя удлинятся они одинаково.
Можно указать свойство flex: 1. Это значит, что элемент будет растягиваться, отталкиваясь от нулевого значения. Потому что, если при использовании сокращённой записи flex не задано flex-basis, оно сбрасывается на 0.
Чтобы создать колонки равной ширины, для grid можно использовать сокращённую запись:
grid: none / auto-flow minmax(min-content, 1fr);
При этом мы хотим, чтобы длинный элемент не переполнялся.
Тут вроде бы всё хорошо, но стоит добавить
overflow: hidden;
как у Flexbox всё едет и длинный элемент начинает обрезаться:
У Grid такой проблемы нет.
Особенности новых раскладок
Всё дело в том, что новые модули задают новые правила. С появлением новых раскладок было введено, что значение min-width по умолчанию равно auto. Для обратной совместимости в старых контекстах раскладки auto вычисляется как 0, но в новых раскладках, таких как Flexbox и Grid Layout, это значение учитывает минимальный размер содержимого. Вот выдержка из спецификации:
»…specifies an automatic minimum size. Unless otherwise defined by the relevant layout module, however, it resolves to a used value of 0.
For backwards-compatibility, the resolved value of this keyword is zero for boxes of all CSS2 display types: block and inline boxes, inline blocks, and all the table layout boxes. It also resolves to zero when no box is generated.»
— CSS Box Sizing Module Level 3
»…задаёт автоматический минимальный размер. Однако, пока иное не задано модулем соответствующей раскладки, используемое значение разрешается как 0.
Для обратной совместимости значение разрешается как ноль для всех типов отображения CSS2: блочных и строчных боксов, инлайн-блоков и всех табличных боксов. Значение разрешается также как ноль, если не сгенерировано ни одного бокса.»
Более того, у grid есть отдельный пункт про расчёт минимального содержимого:
6.6. Автоматический минимальный размер грид-элементов
Для обеспечения grid-элементов разумным минимальным размером, используется значение его автоматического минимального размера для данной оси — минимальный размер содержимого, если выполняется всё следующее:
это не scroll-контейнер;
элемент растянут минимум на один grid-трек по этой оси с автоматическим минимальным размером;
если элемент растянут на более чем один трек по этой оси и ни один из них не является гибким.
Иначе автоматический минимальный размер, как обычно, равен 0.
Также спецификация говорит, чтобы не происходило переполнение для больших элементов, таких как картинки и длинные таблицы, лучше явно задать минимальный относительный размер шрифта. Например, min-width: 12em.
Это поможет избежать наложения или вываливания содержимого из его контейнера. Вот пример, когда происходит горизонтальная прокрутка, там где мы не ожидали. Причём, использование overflow: hidden; во внутренних элементах, не даёт никакого эффекта.
grid-template-columns: auto 1fr;
Поэтому лучше задавать конкретное значение:
min-width: 100px;
Раскладка сразу стала предсказуемой. Поэтому, если вы видели в социальных сетях, что min-width: 0; решает проблемы, теперь вы понимаете почему.
Подведём итог сравнения:
Заключение
Я рассказал вам о преимуществах и недостатках Flexbox и Grid Layout, о которых знаю и которые использую. Теперь вам будет легче решить, каким из них пользоваться самим. Для этого, вам в помощь материалы с интерактивными примерами, шпаргалки и гайды:
Материалы по Flexbox:
Материалы по Grid Layout: