Как работает Flex-grow в CSS. Подробное руководство
Очень много людей у меня спрашивают как правильно работает flex-grow
и как вообще им оперировать. Это конечно очень хитрое свойство, но если в нём разобраться, то оно не представляет собой ничего сложного, нужно только захотеть и приложить небольшие усилия. К сожалению, в рунете я не нашел достойной статьи, которая рассказывает в деталях про flex-grow
, были только небольшие отрывки по работе с дробями.
Но я нашел статью `flex-grow` is weird. Or is it?, которая максимально ясно показывает то, как работает flex-grow
и то, как он рассчитывается. Ниже будет её перевод на русский язык.
👉Мой Твиттер — там много из мира фронтенда, да и вообще поговорим🖖. Подписывайтесь, будет интересно: ) ✈️
Как только я узнал о flex-grow
, то сразу же сделал простое демо, чтобы посмотреть как оно работает и что вообще делает.
Я думал, что во всём разобрался, но когда я попытался применить его на сайте, который недавно сделал мой коллега, то ничего не заработало так, как ожидалось. Что мы только ни делали, а шаблон всё не выглядел и не работал так, как бы мы хотели в моей демке. Это заставило меня переосмыслить всё, что я до этого знал о flex-grow
.
Как flex-grow НЕ работает
Перед тем как мы углубимся в функционал flex-grow
, я бы хотел объяснить вам то, что я сделал неверно в первый раз.
Я думал, что все flex элементы с flex-grow
с параметром 1 будут иметь одинаковую ширину. А если один из элементов будет иметь flex-grow
с параметром 2, то этот элемент будет в два раза больше чем другие в этой группе.
Это звучит великолепно. Кажется, что именно так все и происходит в примере указанном выше. Родительский элемент имеет ширину 900px
, секция с flex-grow:2
получает ширину в 600px
, а боковой элемент с flex-grow:1
имеет ширину 300px
.
Как вы видите, в этой демке все работает просто идеально, но это так не работает в реальной жизни, даже если бы мы использовали тот же самый CSS код. Оказывается, что проблема была не в самом с CSS, а в контенте, вернее в его недостатке. Демка, которую я сделал — она работает, да, но только потому что там использовались два пустых элемента, созданные для тестирования и в итоге оказавшиеся слишком простыми для того, чтобы показать все важные моменты этого свойства.
Как Flex-grow работает на самом деле
Только что я объяснил как flex-grow
не работает, но я показал вам демо, которое на самом деле не делает то, что по моим утверждениям это не делает. Далее в статье я вам объясню причину этого.
Чтобы прояснить ситуацию, позвольте показать вам еще один Pen. В нём такие же установки, как и в первом, но на этот раз секция и боковые элементы не пустые. Теперь соотношение больше не 2:1 и элемент с flex-grow
выставленным на один больше, чем элемент с flex-grow
на 2.
#Объяснение
Если мы применим display:flex
для родительского элемента и ничего больше не поменяем, то дочерние элементы встанут горизонтально, не смотря ни на что. Если будет недостаточно места, то они сократятся в размере. С другой стороны, если будет слишком много места, то они не будут расти, потому что Flexbox хочет, чтобы мы определили насколько им нужно вырасти. Таким образом, прежде чем указывать браузеру каким по ширине должен быть элемент, flex-grow
определяет как оставшееся место распределяется среди flex элементов и насколько велика доля каждого из них.
Или другими словами:
Flex контейнер распределяет свободное место своим элементам, пропорционально их flex-grow
фактору, чтобы заполнить контейнеры или урезать их, пропорционально их flex-shrink
фактору, для того того, чтобы пресечь переполнение контейнера.
#Демонстрация
Эту концепцию гораздо проще понять, если мы её визуализируем.
Во-первых мы выставим display
свойство для нашего родительского элемента на flex и сделав это наши дочерние элементы станут flex-элементами и будут горизонтально расставлены друг за другом.
Далее, мы решаем сколько частей свободного места получит каждый элемент. В нашем предыдущем примере первый элемент получил 2/3 оставшегося места flex-grow:2
и второй элемент 1/3 flex-grow:1
. Зная сколько flex-grow
значений мы можем иметь в сумме, мы будем знать на какое число делить оставшееся место.
Наконец-то у нас есть количество распределяющихся частей. Каждый элемент получает соответствующее количество частей, основываясь на его flex-grow
значении.
#Считаем
Теории и визуальные представления это хорошо, но давайте по пачкаем руки и сделаем математические вычисления для примера выше.
Для наших расчетов понадобится четыре числа: ширина родителя, изначальная ширина секции и бокового элемента и общее количество flex-grow
значений, которые мы будем использовать. Итак, у нас получается:
Ширина родителя: 900px
Ширина секции: 99px
Ширина бокового элемента: 623px
Общее количество flex-grow значений: 3
1. Во первых нам надо рассчитать оставшееся место. Это довольно легко, мы возьмем ширину родителя и вычтем общую ширину изначальных дочерних элементов.
900 — 99 — 623 = 178
2. Далее нам нужно определить размер каждого значения flex-grow
Теперь, когда у нас есть оставшееся место, нам нужно определить на сколько частей мы хотим его разделить. Очень важно понимать, что мы не делим оставшееся место на количество элементов, а делим на общее количество flex-grow
значений. В нашем случае это будет 3 (flex-grow:2
+ flex-grow:1
)
178 / 3 = 59.33
3. Наконец-то разрезанное оставшееся место будет распределено между всеми элементами
Основываясь на их flex-grow
значениях секция получит 2 куска (2*59.33), а боковая сторона получит 1 кусок (1*59.33). Эти числа добавляются к изначальной ширине каждого элемента.
99 + (2 * 59,33) = 217,66 (~218px) | Изначальная ширина секции + (значение flex-grow
для секции * размер одного flex-grow
)
623 + (1* 59.33) = 682.33 (~682px) | Изначальная ширина боковой стороны + (значение flex-grow
боковой стороны * размер одного flex-grow
)
Теперь вообще легко, правда?
Отлично, но почему сработало первое демо?
У нас есть формула, теперь давайте разберемся почему мы получили нужные результаты в первой демке, даже не понимая как мы это делаем.
Ширина родителя: 900px
Ширина секции: 0px
Ширина бокового элемента: 0px
Общее количество
flex-grow
значений: 3
1. Рассчитываем оставшееся место.
900–0–0 = 900
2. Определяем размер каждого значения flex-grow
900 / 3 = 300
3. Распределяем нарезанное свободное место
0 + (2 * 300) = 600
0 + (1 * 300) = 300
Если ширина каждого элемента равна нулю, то оставшееся место равняется настоящей ширине родительского элемента и поэтому все выглядит так, будто flex-grow
разделяет ширину элемента на равные пропорциональные части.
flex-grow и flex-basis
Давайте быстренько повторим: flex-grow
берет оставшееся место и делит его на общее количество flex-grow
значений. Полученный показатель умножается на соответствующее каждой части значение flex-grow
и полученный результат добавляется каждому дочернему элементу с его изначальной шириной.
Но что поделать, если нет оставшегося места или что делать в том случае, если мы не хотим полагаться на изначальную ширину элемента, но значение flex-grow
надо выставить? Мы что, не можем использовать flex-grow
?
Конечно же можем. Есть такое свойство, которое называется flex-basis
, оно определяет изначальный размер элемента. Если вы используете flex-basis
в связке с flex-grow,
то механизм вычисления ширины изменится.
Этот компонент устанавливает flex-basis
, указывая его flex основу: изначальный главный размер flex элемента, перед тем как свободное пространство будет распределено в соответствии с flex факторами.
Тут большая разница в том, что если мы применим flex-basis
для элемента, то для вычислений мы не будем использовать его изначальный размер, а применим значение свойства flex-basis
.
Я адаптировал наш предыдущий пример, добавив flex-basis
для каждого элемента. Вот он. Давайте посмотрим, как это происходит.
1. Рассчитываем оставшееся место
900–400–200 = 300
2. Определяем ширину элемента flex-grow
300/3= 100
3. Распределяем оставшееся место
400 + (2 * 100) = 600
200 + (1 * 100) = 300
Небольшой оффтоп для завершенности дела, вам не нужно использовать исключительно пиксельные значения и надеяться на лучшее, проценты работают так же хорошо.
Работа с box-моделью
Чтобы покрыть вообще все сферы и ситуации, которые случаются с flex-grow
, давайте посмотрим что будет, если мы добавим padding
и margin
. На самом деле ничего особенного. На первом шаге вычислений вам просто нужно не забыть также вычесть внешние отступы.
Единственное, что вам нужно подметить, так это в случае с box-sizing
flex-basis
ведет себя как свойство width
. Это означает то, что вычисления как и результат меняются, если box-sizing
свойство меняется. Если box-sizing
был выставлен на border-box
, то вам надо работать только с flex-basis
и margin
значениями в ваших вычислениях, потому что значение padding
уже включено в ширину.
Несколько полезных примеров
И так, достаточно математики. Давайте посмотрим несколько примеров того, как вы можете эффективно применить flex-grow
в своих проектах.
#Скажи нет width: [x]%
Так как оставшееся место распределяется автоматически, нам не нужно думать о значениях ширины, если мы хотим, чтобы наши дочерние элементы заполняли весь родительский элемент.
#Святой грааль трех колоночного “жидкого” макета с шириной в пикселях
Смеси фиксированных и флюидных широт в column шаблонах возможны с флоатами, но это и не понятно и просто не гибкое решение. Конечно с помощью Flexbox и немного flex-grow
и flex-basis
магии это становится вполне возможным.
#Заполняем оставшееся место любым элементом
Если вы, к примеру, имеете поле ввода прямо за лейблом и хотите, чтобы это поле занимало все оставшееся место, вам больше не нужны убоговатые хаки в верстке. Смотрите.
Прислушиваемся к спецификациям
Следуя спецификации, нам стоит использовать flex сокращения, а не flex-grow
напрямую.
Но будьте аккуратны! Если вы просто будете использовать flex:1;
некоторые из примеров выше не будут работать, потому что значения выставленные для всеобщего удобства не равны дефолтным значениям и это мешает нашим целям.
Если вы хотите использовать flex в нашем случае, то вам нужно определять его параметры вот так:
flex: 2 1 auto;* (<flex-grow> | <flex-shrink> | <flex-basis>) *
Заключение
Flex-grow
сложный и запутанный? Всё совсем не так плохо, как кажется. Нам нужно просто понять как он работает и что он делает. Если у элемента flex-grow
выставлен на 3, это не означает того, что он будет в три раза больше чем элемент у которого flex-grow
стоит с параметром 1. Это будет означать то, что он получит в три раза больше пикселей к изначальной ширине, чем другой элемент.
Я выучил свой урок, тестируя flex-grow
с двумя пустыми элементами, которые дали мне совершенно другое понимание того, что это свойство делает и это само собой привело меня к неверным заключениям. Вам нужно проверять новые фишки в среде, которая максимально приближена к реальности, тогда вы получите максимально реалистичное понимание того, как это работает и себя ведет.