CSS – Флексбокс

Флексбокс — это первый CSS-механизм, предназначенный для построения сеток и создания сложных раскладок блоков.

Другие механизмы, с помощью которых мы раньше строили сетки, задумывались совсем не для этого: плавающие блоки нужны для создания блоков, которые обтекает текст, а таблицы используются для разметки табличных данных.

Для активации этого нового механизма необходимо элементу свойство display: flex;. После этого происходит два события:

  1. Элемент с display: flex; превращается во «флекс-контейнер» и внутри него начинает происходить вся магия гибкой раскладки.
  2. Непосредственные потомки этого элемента превращаются во «флекс-элементы» и начинают играть по новым правилам.

Первое, что вы заметите после активации, это то, что блоки растянутся на всю высоту контейнера и самое важное внутри флексбокса можно делать элементы одинаковой высоты!

Заметьте, что флекс-элементами внутри флекс-контейнера становятся только прямые потомки, элементы первого уровня вложенности.

Вспомните, как ведёт себя обычный поток документа. Блоки и текст располагаются слева направо и сверху вниз.

В привычной блочной модели направления «лево», «право», «верх» и «низ» неизменны. Но внутри флекс-контейнера эти понятия могут изменяться, потому что там можно изменять обычное направление потока.

Вместо направлений «лево» и «право» во флексбоксе используется понятие «главная ось». Поток флекс-элементов «течёт» вдоль главной оси от её начала к её концу.

По умолчанию главная ось направлена слева направо, но её можно разворачивать во всех направлениях с помощью свойства flex-direction, которое задаётся для флекс-контейнера. Значения свойства:

  • row — значение по умолчанию, главная ось направлена слево направо.
  • column — главная ось направлена сверху вниз.
  • row-reverse — главная ось направлена справа налево.
  • column-reverse — главная ось направлена снизу вверх.

Флекс-элементы всегда располагаются вдоль главной оси, независимо от её направления.

Вместо направлений «верх» и «низ» во флексбоксе используется понятие «поперечная ось». Вдоль этой оси рассчитывается высота элементов и работают «вертикальные» выравнивания.

Поперечная ось всегда перпендикулярна главной оси и поворачивается вместе с ней:

  • Если главная ось направлена горизонтально, то поперечная ось смотрит вниз.
  • Если главная ось направлена вертикально, то поперечная ось смотрит направо.

Это не совсем логичное поведение, к которому надо привыкнуть. Получается, что поперечная ось никогда не смотрит вверх или влево. А свойства для поворота поперечной оси нет.

Вместо «горизонтального» выравнивания во флексбоксе используется свойство для распределения элементов вдоль главной оси — justify-content. Это свойство задаётся для флекс-контейнера.

Его значением по умолчанию является flex-start. При этом значении элементы располагаются у начала главной оси.  Оно же является и значением по умолчанию.

Чтобы элементы располагались по центру главной оси, нужно задать для justify-contentзначение center.

А значение flex-end расположит элементы в конце главной оси.

Обратите внимание, что justify-content: flex-end не меняет порядок элементов, как это происходит при изменении направления оси flex-direction: row-reverse. Элементы просто прижимаются к концу главной оси.

У флексбокса есть чем-то похожие значения justify-content, которые равномерно распределяют флекс-элементы вдоль главной оси: space-between — расстояния между соседними элементами одинаковые, между элементами и краями флекс-контейнера отступов нет. space-around — расстояния между соседними элементами одинаковые, между элементами и краями флекс-контейнера есть отступ, равный половине расстояния между соседними элементами.

Вместо «вертикального» выравнивания во флексбоксе используется свойство для выравнивания элементов вдоль поперечной оси — align-items. Это свойство задаётся для флекс-контейнера.

Его значением по умолчанию является stretch. Именно благодаря этому значению флекс-элементы и растягиваются на всю «высоту» флекс-контейнера. Если флекс-элементам задана высота, то растягиваться они не будут.

Чтобы элементы выровнялись по центру поперечной оси, нужно задать для align-itemsзначение center.

Чтобы расположить флекс-элементы в начале или в конце поперечной оси, нужно использовать значения flex-start и flex-end для свойства align-items.

Ещё одно значение свойства align-items — это baseline. Если задать его контейнеру, то флекс-элементы будут выравниваться по базовой линии текста в них. Эта воображаемая линия проходит по нижней части букв.

Если выровнять флекс-элементы по базовой линии, то они выстроятся так, чтобы текст в них был как бы на «одной строке».

Распределение элементов по главной оси задаётся для всего флекс-контейнера и на все флекс-элементы действует одинаково. Задать какому-то элементу отличное от других распределение по главной оси нельзя. И это вполне логично, ведь тогда элементы будут «сталкиваться» друг с другом.

C поперечной осью всё проще. Можно сказать, что у каждого элемента она своя, и можно задавать им разное поперечное выравнивание. Для этого используется свойство align-self, которое задаётся для самих флекс-элементов, а не для флекс-контейнера.

У свойства align-self те же самые значения, что и у align-items. Аналогично align-items для отдельного флекс-элемента можно задать и выравнивание по базовой линии с помощью align-self со значением baseline.

Что будет, если флекс-элементов в контейнере станет больше, чем может уместиться в один ряд?

  • Они будут сжиматься до минимально возможной ширины.
  • Даже если им задать ширину явно, механизм флексбокса может её уменьшить.
  • Если они перестанут помещаться в контейнер и после уменьшения, то они выйдут за его пределы, но продолжат располагаться в один ряд.

Это чем-то похоже на поведение ячеек в таблице.

Такое поведение можно изменить свойством флекс-контейнера flex-wrap. По умолчанию оно имеет значение nowrap, то есть перенос флекс-элементов на новую строку запрещён.

Значение wrap разрешает перенос флекс-элементов на новую строку, если они не помещаются в контейнер.

Если перенос флекс-элементов разрешён, то ряды элементов располагаются вдоль поперечной оси. Первый ряд располагается в начале поперечной оси, а последний в конце. Но так работает только значение wrap.

Если для flex-wrap задать значение wrap-reverse, то элементы будут переноситься, а ряды будут располагаться в обратном порядке: первый в конце поперечной оси, а последний в начале.

Вспомним свойство justify-content, которое управляет распределением флекс-элементов вдоль главной оси.

Есть очень похожее свойство align-content, которое управляет выравниванием рядов флекс-элементов вдоль поперечной оси. У этих свойств почти одинаковые значения:

  • flex-start,
  • flex-end,
  • center,
  • space-between,
  • space-around,
  • и stretch, которое есть только у align-content и является значением по умолчанию.

Свойство align-content связано и со свойством align-items, которое управляет выравниванием флекс-элементов вдоль поперечной оси.

В чём разница между align-content и align-items, когда работает одно, а когда работает другое? Вот ответы:

  • Если есть только один ряд флекс-элементов, то работает align-items.
  • Если есть несколько рядов, то работает align-content.
  • Подчеркнём, что align-content влияет на ряды, а не на отдельные элементы.

В последней версии спецификации это поведение изменилось: теперь правильно, когда align-content выравнивает элементы в многострочном флекс-контейнере, даже если строка в контейнере единственная. Это изменение на момент окончания 2015 года применено только в браузерах Safari и Edge.

Ранее мы говорили, что как только во флекс-контейнере появляется несколько рядов элементов, вместо align-items начинает действовать свойство align-content.

В этом случае align-items не отключается полностью, а может влиять на отображение флекс-элементов в рядах.

Это происходит, когда мы используем для align-content значение по умолчанию — stretch. Оно растягивает ряды флекс-элементов, при этом оставшееся свободное место между ними делится поровну.

Отображение строк при align-content: stretch зависит от значения align-items:

  • Если у align-items задано значение stretch, то элементы в строках растягиваются на всю высоту своей строки.
  • Если значение отлично от stretch, то элементы в строках ужимаются под своё содержимое и выравниваются в строках в зависимости от значения align-items.

Значение align-items влияет на отображение рядов во флекс-контейнере, если у align-content задано значение stretch. В этом мы убедились в прошлом задании.

Есть ли похожее влияние на остальные значения align-content? Нет.

Убедимся в этом на примере значения center, которое располагает ряды в середине поперечной оси так, что:

  • отступов между соседними рядами нет (но отступы самих элементов сохраняются),
  • расстояние между первым рядом и краем флекс-контейнера равно расстоянию между последним рядом и другим краем.

Остальные четыре значения свойства align-content аналогичны значениям свойства justify-content, отличается только ось:

  • flex-start располагает ряды в начале поперечной оси.
  • flex-end располагает ряды в конце поперечной оси.
  • space-between равномерно распределяет ряды вдоль поперечной оси, расстояния между соседними рядами одинаковые, отступов у краёв нет.
  • space-around равномерно распределяет ряды вдоль поперечной оси, расстояния между соседними рядами одинаковые, отступы у краёв равны половине расстояния между соседними рядами.

Напоследок небольшое резюме.

Свойство align-content — «гибридное». Мы переводим его как «выравнивание», но оно больше похоже на «распределение», justify-content, от которого оно позаимствовало два значения space-between и space-around.

Близость с «распределением» подчёркивает и отсутствие значения baseline — всё-таки свойство работает с рядами, а не с отдельными элементами.

От «выравниваний» же, align-items и align-self, это свойство получило значение по умолчанию stretch и возможность «растягивать» ряды по высоте.

И ещё одно свойство, которое мы рассмотрим, — это order, порядковый номер флекс-элемента.

Это очень полезное свойство, так как с его помощью можно менять порядок следования флекс-элементов в потоке, не меняя HTML-код. По умолчанию порядковый номер флекс-элементов равен 0, а сортировка элементов производится по возрастанию номера.Порядковый номер задаётся целым числом, положительным или отрицательным.

Флекс-элемент внутри флекс-контейнера можно также сделать флекс-контейнером.

Он будет одновременно вести себя и как флекс-элемент, то есть подчиняться правилам распределения и выравнивания своего родителя, и как самостоятельный флекс-контейнер. При этом его вложенные элементы первого уровня будут следовать только его флекс-правилам.

Применение такого типа верстки:

Самый насущный вопрос, который можно легко и изящно решить с помощью флексбокса, — как отцентровать элемент по вертикали и горизонтали так, чтобы центровка сохранялась при изменении размеров элемента или контейнера.

Ответ прост: задать контейнеру раскладку флексбокса, а дочернему флекс-элементу margin: auto. В этом случае флекс-элемент уменьшит свой размер под содержимое и отцентруется по вертикали и горизонтали.

Стоит обратить внимание на интересный момент. Если центруемых флекс-элементов в контейнере будет несколько, то отступы между ними будут равномерными. То есть будет происходить распределение элементов внутри флекс-контейнера чем-то похожее на justify-content: space-around.

“Гибкое меню”

Флексбокс полезен при создании блоков с дочерними элементами динамической длины. Хороший пример — меню. Часто встречаются дизайны, в которых пункты равномерно распределены по блоку меню. Первый пункт примыкает к левой части блока меню, а последний — к правой, причём с небольшими внутренними отступами. Эту задачу можно попытаться решить, задав фиксированные отступы и ширину пунктам меню. Но такой способ не подойдёт, если количество пунктов меню или подписи внутри них будут изменяться.

И тут на помощь приходит флексбокс. Зададим меню раскладку флексбокса, пункты станут флекс-элементами. С помощью свойства распределения элементов justify-content добьёмся нужного результата.

Интересного эффекта можно достичь, если скомбинировать флексбокс и трюк с селектором :checked ~.

Приём заключается в следующем: с помощью селектора по выделению чекбокса можно управлять порядком флекс-элементов, изменяя направление главной оси с помощью flex-direction. Лучше всего эффект работает, когда направление главной оси меняется с «сверху вниз» на «снизу вверх».

При этом флекс-контейнер должен находиться в разметке на одном уровне с чекбоксом.

Таким образом реализуется сортировка на CSS, без использования JavaScript.

Ещё одна часто встречающаяся задача — реализовать раскладку с блоками одинаковой высоты.

Надо учитывать, что содержимое блоков может быть разным и их высота может меняться.

В обычной блочной модели есть фундаментальный недостаток — соседние блоки ничего не знают друг о друге, поэтому их высоты нельзя «связать». Получается, что все «стандартные» варианты для решения этой задачи не работают:

  • float или inline-block не могут «связывать» высоты соседних блоков;
  • таблицы и CSS-таблицы позволяют делать ячейки одинаковой высоты, но появляются ограничения, связанные с расположением элементов в строках;
  • минимальная высота не подходит, так как какой-то из блоков всегда может стать выше остальных;
  • конечно, можно задать всем блокам фиксированную высоту, но это решение совсем не универсальное.

А с помощью флексбоксов эта проблема решается легко и изящно, ведь флекс-элементы по умолчанию растягиваются на всю высоту контейнера.

Например:

Флекс и блочная модель

Ширина, высота, внутренние отступы и рамки для флекс-контейнеров и флекс-элементов работают как обычно: общий размер элементов складывается из этих компонентов. Это поведение так же можно менять с помощью свойства box-sizing.

Есть и несколько важных отличий:

  • флекс-элементы, в отличие от блочных элементов, не растягиваются на всю ширину контейнера по умолчанию;
  • на флекс-элементы не действует свойство float.

Свойство margin, таит много сюрпризов:

  • внешние отступы не схлопываются, ни по горизонтали, ни по вертикали;
  • внешние отступы не выпадают, ни из флекс-контейнера, ни из флекс-элементов;
  • значение auto получило премию журнала Форбс в номинации «Самое влиятельное значение CSS-свойства внутри флекс-контейнера».

Всё дело в механизме распределения свободного места. Если внутри флекс-контейнера есть свободное место, то оно перераспределяется так:

  1. находятся элементы, у которых есть внешние отступы со значением auto;
  2. всё свободное место в соответствующих направлениях отдаётся таким отступам (то есть задаётся определённый размер отступа в пикселях);
  3. если элементов с автоматическими отступами на одном направлении несколько, то место между ними перераспределяется поровну;
  4. только после этого запускаются механизмы выравнивания.

Поэтому margin: auto; влияет на положение флекс-элементов на обеих осях, а также «ломает» механизмы выравнивания, ведь выравнивание происходит, когда есть свободное место. Но если всё свободное место «перетекло» в автоматический отступ, то и выравнивать нечего. Эти особенности можно использовать во благо. Например, с помощью автоматических отступов вы можете управлять расположением элементов вдоль главной оси.

Кроме того, мы убедились, что такие отступы «ломают» свойство justify-content.

Автоматический margin влияет и на выравнивание флекс-элементов вдоль поперечной оси.

Если у флекс-элемента отступ сверху или снизу автоматический, то на него не влияют, ни align-items, ни align-self. Такой элемент прижмётся либо к верху контейнера, либо к низу.

А если задать автоматические отступы с противоположных сторон, то элемент разместится по центру флекс-контейнера, так как свободное место «впитается» отступами поровну.

Дело в том, что «старые нефлексовые» свойства, такие как отступы или размеры, ничего не знают про направление осей. Они «мыслят по-старому», понятиями «верх» и «низ», «право» и «лево».

Поэтому когда главная ось направлена слева направо, горизонтальные отступы перемещают флекс-элементы вдоль главной оси. Но если направить главную ось сверху вниз, то те же отступы начнут работать вдоль поперечной оси. То же относится и к вертикальным отступам.

На примере отступов видно, что «старые» свойства внутри флекс-контейнера ведут себя достаточно глупо. Ширина и высота тоже не умеют реагировать на поворот главной оси. Поэтому ввели понятия главный размер или main size и поперечный размер или cross size.

Если главная ось направлена горизонтально, то главный размер — это ширина, свойство width, а поперечный размер — это высота, свойство height. Если главная ось направлена вертикально, то всё наоборот.

А хотелось бы иметь «умное» свойство для задания размера флекс-элементов, которое знает про главную ось и «поворачивается» вместе с ней.

И такое свойство есть — это flex-basis. Оно задаёт базовый размер флекс-элемента или размер вдоль главной оси.

Если flex-basis не задан или его значение равно auto, то базовый размер берётся из width или height.

Свойство flex-basis принимает те же значения, что и width или height.

Свойство flex-basis «сильнее» свойств width и height, и если у флекс-элемента заданы все три свойства, то flex-basis переопределит либо ширину, либо высоту в зависимости от направления главной оси

На самом деле, базовый размер — это не просто размер элемента вдоль главной оси, это начальный или исходный размер вдоль главной оси.

Почему так важны эти начальный или исходный? И снова всё дело в механизме перераспределения свободного места во флексбоксе.

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

Свойство flex-grow принимает неотрицательные числовые значения, его значение по умолчанию — 0.

Если значение flex-grow равно нулю, то флекс-элемент «не претендует» на оставшееся свободное место во флекс-контейнере и не будет увеличиваться, чтобы занять это место.

Если значение flex-grow больше нуля, то флекс-элемент будет увеличиваться, «захватывая» оставшееся свободное место.

Получается, что базовый размер — это исходный размер флекс-элементов до применения flex-grow.

Если сразу у нескольких флекс-элементов значение flex-grow больше нуля, то они будут делить свободное место между собой.

Свободное место будет добавляться флекс-элементам пропорционально значениям их «коэффициента жадности».

Расчёт итогового размера с flex-grow

1 шаг. Рассчитываем свободное место во флекс-контейнере:

2 шаг. Считаем размер минимальной доли свободного места:

3 шаг. Базовый размер каждого флекс-элемента увеличиваем на размер минимальной доли свободного места умноженной на значение flex-grow этого элемента:

Но если задать флекс-элементам нулевой базовый размер, свободное место будет занимать всю ширину флекс-контейнера, и коэффициенты жадности будут другими.

Использовать flex-basis: 0 и flex-grow для точного управления относительными размерами не стоит. Лучше использовать базовый размер в процентах.

Тонкость. На размер оставшегося свободного места влияет не только flex-basis, но и рамки, и отступы. Если flex-basis явно задано нулевое значение, то min-width на размер свободного места влиять не будет, так как ограничения размеров к флекс-элементам применяются уже после перераспределения свободного места.

Если сумма базовых размеров флекс-элементов больше, чем размер флекс-контейнера, то возникает отрицательное пространство.

Механизм перераспределения работает не только для свободного места, но и для отрицательного пространства. Флекс-элементы умеют распределять отрицательное пространство между собой и сжиматься.

За уменьшение флекс-элементов отвечает свойство flex-shrink, которое можно назвать «коффициентом сжатия».

Свойство flex-shrink принимает неотрицательные числовые значения, его значение по умолчанию — 1.

Если значение flex-shrink больше нуля, то флекс-элемент будет уменьшаться, «впитывая» часть отрицательного пространства, если оно существует.

Если значение flex-shrink равно нулю, то флекс-элемент уменьшаться не будет.

Флекс-элементы стараются быть максимально «гибкими» и не выпадать из своего контейнера, поэтому у flex-shrink значение по умолчанию равно 1. Но если задавать нулевые значения для коэффициента сжатия, то выпадения элементов добиться можно.

Свойство flex-shrink очень похоже на свойство flex-grow. Оно задаёт пропорции, в которых флекс-элементы «впитывают» отрицательное пространство.

Чем больше значение коэффициента сжатия у элемента, тем больше отрицательного пространства он «впитает» и тем сильнее сожмётся.

Когда базовые размеры флекс-элементов одинаковы, пропорции сжатия элементов считаются так же, как пропорции увеличения. Если базовые размеры флекс-элементов отличаются, то механизм усложняется.

Расчёт итогового размера с flex-shrink [12/28]

Ниже описан механизм расчёта размеров элементов, когда места в контейнере не хватает:

1 шаг. Рассчитываем отрицательное пространство (ОП) во флекс-контейнере:

2 шаг. Находим сумму произведений базовых размеров (СПБР) элементов на их коэффициенты сжатия:

3 шаг. Для каждого элемента считаем «нормированный коэффициент сжатия» (НКС), для чего произведение базового размера элемента на его коэффициент сжатия делим на СПБР:

4 шаг. Базовый размер элемента уменьшаем на часть ОП пропорциональную НКС элемента:

Получается, что доля отрицательного пространства, которую «впитает» элемент, зависит от двух факторов:

  • соотношения коэффициента сжатия элемента с коэффициентами других элементов,
  • соотношения базового размера элемента с базовыми размерами других элементов.

Именно поэтому в формулах присутствуют нормировки.

Есть несколько тонкостей, касающихся сжатия флекс-элементов:

  • элементы сжимаются в пределах своих базовых размеров, внутренние отступы и рамки не сжимаются;
  • «ограничительные» свойства, такие как min-width, применяются к элементам после этапа перераспределения свободного места или отрицательного пространства.

И эти тонкости могут приводить к неожиданным эффектам, когда элементы выпадают из флекс-контейнера.

Обобщенное свойство

С помощью сокращённого свойства flex можно одновременно задать коэффициенты растягивания, сжатия и базовый размер флекс-элемента.

Свойство flex состоит из трёх компонентов, которые пишутся через пробел в следующем порядке: flex-grow, flex-shrink и flex-basis. В примере ниже два правила аналогичны:

Ещё у свойства flex есть особые значения: initial, auto, none. Также второй и третий компоненты необязательны. Ниже показаны различные значения свойства и их расшифровки.

В некоторых браузерах неполные или особенные значения свойства flex интерпретируются с ошибками. Поэтому лучше задавать все три компоненты в значении этого свойства.

flex-wrap: nowrap;.

А как будут растягиваться и сжиматься элементы в многострочном контейнере, с flex-wrap: wrap;?

В таком контейнере свойство flex-shrink будет работать как обычно, но необходимость в нём будет возникать намного реже. Ведь при нехватке места в строке флекс-элементы будут переноситься на новую строку.

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

В отличие от flex-shrink, свойство flex-grow в многострочном флекс-контейнере срабатывает намного чаще и пользы приносит намного больше.

В каждой строке такого контейнера может быть свободное место и механизм перераспределения этого места работает построчно.

Поэтому возможность «растянуть» флекс-элементы, чтобы строки заполнялись по ширине полностью, будет возникать достаточно часто.

flex-basis: 100% и flex-wrap

Познакомимся с интересным эффектом, который возникает при использовании базовых размеров в процентах.

Если задать базовый размер флекс-элемента 100% и при этом включить перенос элементов на новую строку, то элементы расположатся столбцом, хотя главная ось контейнера будет по-прежнему направлена слева направо.

Заголовок с описанием на флексбоксах

Мы рассмотрели теоретическую часть и приступаем к практической части этого курса. Давайте разберём на реальных элементах интерфейсов, в каких случаях бывает уместно использовать флексбокс.

Довольно распространённый ход в интерфейсе — блок с заголовком и небольшим уточняющим подзаголовком на одной строке. Заголовок находится в начале строки, а подзаголовок прижат к правому краю.

Если текст заголовка или подзаголовка сильно увеличится, то вёрстка не должна ломаться — тексты просто должны расположиться друг под другом.

Сверстать элемент с таким гибким поведением с помощью float или display: tableне получится. Ведь нужно, чтоб блоки подписей одновременно и занимали свободное пространство, и чтобы их размеры зависели от текстового содержания, и чтобы в случае переполнения сетка перестраивалась.

Здесь нам поможет только флексбокс с flex-grow.

Ещё один случай, когда может пригодиться флексбокс — поля ввода с динамической шириной. Требования к ним такие:

  • На одной строке с полем могут находиться другие элементы: кнопки, ссылки, подписи.
  • Размер дополнительных элементов не определён, он зависит от их содержимого.
  • При этом поле должно растягиваться на всё оставшееся в родительском контейнере место.
  • И изменять ширину при изменении размеров контейнера.

Решить эту задачу можно только при помощи флексбокса. Превратим контейнер поля ввода во флекс-контейнер, все элементы внутри него превратятся во флекс-элементы, базовый размер которых будет зависеть от их содержания — flex-basis: auto;. И останется только задать ненулевой коэффициент растягивания полям ввода.

В широком контейнере всё будет работать отлично. Проблемы могут появиться в слишком узких контейнерах: по умолчанию поля ввода не будут сжиматься после определённой ширины, что приведёт к выпаданию текста из остальных элементов.

Чтобы справиться с этими проблемами, надо задать всем элементам кроме полей ввода нулевой коэффициент сжатия, а самим полям ввода явно прописать минимальную ширину.

Пример:

 

Обсуждение закрыто.