CSS-live.ru

flex-grow странный. Так ли это?

Перевод статьи `flex-grow` is weird. Or is it?s с сайта css-tricks.com, опубликовано на css-live.ru, автор — Мануэль Матузовик.

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

Когда я узнал про flex-grow, то тут же сделал простой пример, чтобы понять для чего оно и как работает. Я думал, что во всём разобрался, но когда я решил попробовать его на сайте, который смастерил мой коллега, то оно меня подвело. И что бы мы ни делали, раскладка выглядела и вела себя не так, как в том примере. И я поневоле засомневался, а понял ли я вообще хоть что-то про flex-grow.

Как flex-grow не работает

Прежде чем погрузиться в функциональность flex-grow, я бы хотел объяснить, где ошибся сначала.

Я подумал, что все flex-элементы с flex-grow:1 будут равной ширины. А если одному из элементов установить flex-grow:2, то он будет вдвое больше остальных.

Звучит неплохо. Кажется, что именно это и происходит в ранее упомянутом примере на CodePen. Ширина родительского элемента 900px, вычисленная ширина элемента section с flex-grow:2 600px, а элемента aside с flex-grow:1 — 300px.

Как видите, в демо-примере всё работает замечательно, в отличие от примера из реальной жизни, где это не работало вообще, даже с одинаковым CSS. Как выяснилось, проблема была не в CSS, а в контенте (точнее в его отсутствии). Демо-пример работает, поскольку в нём используются только два пустых элемента, и он слишком прост, чтобы отобразить важные особенности свойства.

Как flex-grow действительно работает

Я только что описал, как flex-grow не работает, но показал демо-пример, который делает как раз то, чего, по моим словам, делать не должен (позже я объясню причину)

Чтобы прояснить ситуацию, я сделал ещё один пример на Codepen. Настройки остались такими же, но на этот раз в элементах section и aside есть контент. Теперь соотношение уже не 2:1, и элемент с flex-grow:1 фактически больше элемента с flex-grow:2.

Объяснение

У родительского элемента с display: flex; (без дополнительных настроек) дочерние элементы выстраиваются друг за другом горизонтально, несмотря ни на что. При нехватке свободного места элементы сужаются. Но если места предостаточно, элементы не растягиваются, поскольку Flexbox оставляет за нами право решать, насколько должны растягиваться элементы. Поэтому, вместо того, чтобы сообщить браузеру ширину элемента, flex-grow определяет, как оставшиеся пространство распределяется между flex-элементами, и доли каждого из них.

Иначе говоря:

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

https://drafts.csswg.org/css-flexbox/#flexibility

Демонстрация

Эту идею гораздо легче понять на наглядном примере.

Для начала установим свойству display родительского элемента значение flex, и теперь дочерние элементы становятся flex-элементами и располагаются горизонтально друг за другом.

step1

Затем определимся с долями дополнительного пространства для каждого элемента. В предыдущем примере первый элемент получает 2/3 оставшегося пространства (flex-grow: 2), а второй — 1/3 (flex-grow: 1). Зная общее количество значений flex-grow, мы получаем число, на которое делим оставшееся пространство.

step2

И наконец, мы получили число долей для распределения. Каждый элемент получает соответствующее число долей, в зависимости от значения flex-grow.

step3

Вычисление

Теория и наглядный пример — это приятно, но давайте не поленимся и посчитаем этот пример сами.

Для вычисления понадобятся четыре числа: ширина родительского элемента, исходная ширина элементов section и aside и общее число значений flex-grow, которое мы собираемся использовать.

родительская ширина: 900px
ширина section: 99px
ширина aside: 623px
сумма значений flex-grow: 3

1. Для начала нужно вычислить оставшееся пространство

Это не сложно. Возьмём родительскую ширину и вычтем из неё общую начальную ширину каждого дочернего элемента.

900 - 99 - 623 = 178

родительская ширина − начальная ширина section − начальная ширина aside = оставшееся пространство

2. Далее нужно определить, сколько приходится на один flex-grow

Вычислив оставшееся пространство, нужно определить, на сколько долей его разделить. Здесь важно понимать, что мы делим оставшееся пространство не на число элементов, а на сумму значений flex-grow. Так что в нашем случае это 3 (flex-grow: 2 + flex-grow: 1)

178 / 3 = 59.33

оставшеся пространство/сумма значений flex-grow = «один flex-grow»

3. И наконец, доли оставшегося пространства распределяется между всеми элементами

Исходя из их значений flex-grow, section получает две доли (2 * 59.33), а aside — 1 (1 * 59.33). Эти числа добавляются к начальной ширине каждого элемента.

99 + (2 * 59.33) = 217.66 (≈218px)

начальная ширина section + (значение flex-grow у section * «один flex-grow») = новая ширина

и

623 + (1 * 59.33) = 682.33 (≈682px)

начальная ширина aside + (значение flex-grow у aside * «один flex-grow») = новая ширина

Элементарно, так ведь?

Хорошо, но почему первый демо-пример работает?

Формула получена, и теперь давайте выясним, как нам удалось заполучить фактически нужные числа в первом Codepen, даром что мы действовали совершенно наобум.

родительская ширина: 900px
ширина section: 0px
ширина aside: 0px
сумма значений flex-grow: 3

1. Вычисляем оставшееся пространство

900 - 0 - 0 = 900

2.  Определяем, сколько приходится на один flex-grow

900 / 3 = 300

3. Распределяем доли оставшегося пространства

0 + (2 * 300) = 600
0 + (1 * 300) = 300

Если ширина каждого элемента — 0, это оставшееся пространство равно фактической ширине родительского элемента, и создается впечатление, что flex-grow делит ширину родительского элемента на пропорциональные доли..

flex-grow и flex-basis

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

А если оставшегося пространства нет или вместо начальной ширины элементов нам хочется задействовать установленное нами значение? Можно ли и в этом случае использовать flex-grow?

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

<‘flex-basis’>. Этот компонент устанавливает отдельное свойство flex-basis и указывает базовый размер: начальный главный размер flex-элемента до распределения свободного пространства в соответствии с flex-grow и flex-shrink.

https://drafts.csswg.org/css-flexbox/#valdef-flex-flex-basis

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

Я подправил предыдущий пример, добавив к каждому элементу flex-basis. Вот результат.

родительская ширина: 900px
ширина section: 400px (значение flex-basis)
ширина aside: 200px (значение flex-basis)
сумма значений flex-grow: 3

1. Вычисляем оставшееся пространство

900 - 400 - 200 = 300

2.  Определяем, сколько приходится на один flex-grow

300 / 3 = 100

3. Распределяем доли оставшегося пространства

400 + (2 * 100) = 600
200 + (1 * 100) = 300

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

Работа с боксовой моделью

Чтобы уж точно ничего не бояться, проверим, что произойдёт при добавлении padding и margin. В принципе, ничего особенного. На первом шаге вычисления просто не забывайте вычесть также и отступы.

Единственное, что стоит отметить — что с точки зрения box-sizing поведение flex-basis соответствует свойству width. А значит, при изменении свойства box-sizing изменится вычисление, а следовательно и результаты. Если box-sizing присвоить значение border-box, то в вычислении участвовали бы только значения flex-basis и margin, поскольку padding уже включён в ширину.

Несколько полезных примеров

Ладно, оставим математику. Я покажу несколько примеров, как можно эффективно использовать flex-grow в проектах.

Больше никаких width: [ x ]%

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

See the Pen flex-grow by Manuel Matuzovic (@matuzo) on CodePen.

Резиновая трехколоночная раскладка («Святой Грааль») с фиксированными колонками в пикселях

Смешивание фиксированных и плавающих ширин в колоночных раскладках возможно и с float, но это сложно, не очевидно и негибко. Конечно, для Flexbox с небольшой магией flex-grow и flex-basis это пустяки.

See the Pen Layout using fluid and fixed widths by Manuel Matuzovic (@matuzo) on CodePen.

 

Заполнение любым элементом оставшегося пространства

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

See the Pen Filling up the remaining space in a form by Manuel Matuzovic (@matuzo) on CodePen.

Ещё больше примеров можно найти на сайте «Решено с помощью Flexbox» Филипа Уолтона.

Что говорит спецификация

Согласно спецификации, вместо flex-grow нам следует использовать короткую запись flex.

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

https://drafts.csswg.org/css-flexbox/#flex-grow-property

Но будьте осторожны! С простым flex: 1; некоторые примеры выше перестанут работать, поскольку значения, установленные для типичных случаев, не соответствуют значениям по умолчанию, и поэтому могут оказаться не тем, что надо.

Если хотите использовать flex в нашем случае, то лучше указывать его примерно так:

flex: 2 1 auto;  /* (<flex-grow> | <flex-shrink> | <flex-basis>) */

Где можно изучить Flexbox

Если хотите подробнее изучить Flexbox, воспользуйтесь этими ресурсами:

Резюме и усвоенные уроки

flex-grow странный? Неа, ничуть. Нам просто нужно понять как он работает и что делает. Элемент с flex-grow:3 не будет втрое больше элемента с flex-grow:1, а к его начальной ширине добавится в 3 раза больше пикселей, чем к другому элементу.

Я извлёк урок из тестирования flex-grow c двумя пустыми элементами, из-за которого я сначала неправильно понял, как оно работает, и, естественно, пришел было к неправильным выводам. Следует проверять новые возможности в максимально реалистичной среде, чтобы лучше представлять, как они работают и ведут себя.

P.S. Это тоже может быть интересно:

10 комментариев

  1. Есть один нюанс при использовании свойства flex-basis. Интересно то, что когда два других параметра общего свойства flex (flex-grow и flex-shrink) делают элемент гибким, то flex-basis не влияет на размер родителя, и он зависит от размера окна, в случае flex’a, и от размера строки, в случае inline-flex’a, или от содержимого, когда размер строки достаточен, чтоб оно в нём полностью поместилось.
    Размеры flex-basis, в этом случае, влияют лишь на соотношение размеров элементов флекс-контейнера.
    Например, если у нас флекс-контейнер и у него два гибких дочерних элемента (flex-grow и flex-shrink у них, например по единице, что означает свободное растяжение и сжатие, в равной степени, для каждого элемента), то если размеры flex-basis, будут равны, соответственно, 400px и 800px, то это означать будет только то, что размеры этих блоков будут соотносится как один к двум (но не меньше самого длинного слова в них или иного фиксированного компонента), а размер самих боксов будет при этом зависеть от внешних парметров, указанных выше.

  2. Что за оставшееся пространство и откуда оно взялось? Это самое запутанное и непонятное объяснение, что я встречал.

    1. Разница между доступной шириной контейнера и суммой базовых ширин (которые flex-basis) флекс-элементов. Разве картинка не помогает?

      1. А как рассчитать flex-grow для элементов с flex-basis: auto? Допустим 4 элемента c grow: 1 и 5-ый с grow: 2. Вот пример: https://codepen.io/Gaxak/pen/eVLxoR

        1. Не очень ясна постановка задачи. Нужно, чтобы элементы растягивались пропорционально длине контента?

  3. Извините, но я вообще не понял в чем разница расчетов. «Если вы используете flex-basis в сочетании с flex-grow, то способ вычисления ширин меняется.» Но в  примерах без и с flex-basis все одинаково:

    рассчитываем оставшееся пространство
    Определяем, сколько приходится на один flex-grow
    финальные величины элементов = изначальные + значение flex-grow, умноженное на то, сколько приходится на один flex-grow

    Объясните, пожалуйста, в чем же разница.

    1. Разница в том, что считать оставшимся пространством (и соответственно, что считать изначальными размерами элементов). Остальной алгоритм, да, тот же самый.

Добавить комментарий для Сергей Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *

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