Как работает Flex-grow в CSS. Подробное руководство

Stas Bagretsov
8 min readJan 18, 2018

Очень много людей у меня спрашивают как правильно работает 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 с двумя пустыми элементами, которые дали мне совершенно другое понимание того, что это свойство делает и это само собой привело меня к неверным заключениям. Вам нужно проверять новые фишки в среде, которая максимально приближена к реальности, тогда вы получите максимально реалистичное понимание того, как это работает и себя ведет.

--

--

Stas Bagretsov

Надеюсь верую вовеки не придет ко мне позорное благоразумие. webdev/sports/books