Flexbox вглубь: grow, shrink, basis
Разбираем три числа за свойством flex и понимаем, как элементы делят место, растут и сжимаются.
flex — это шорткат из трёх значений:
flex-grow(как расти),flex-shrink(как сжиматься) иflex-basis(стартовый размер до распределения).
Зачем это знать на практике
Большинство «непонятных» багов Flexbox — это непонимание трёх чисел внутри flex. Почему один блок не сжимается? Почему flex: 1 игнорирует заданную ширину? Почему текст вылезает за контейнер, хотя стоит overflow: hidden? Все ответы — в механике grow/shrink/basis и в коварном дефолтном min-width: auto. Разобравшись, вы перестанете подбирать значения наугад и будете писать раскладку, которая ведёт себя предсказуемо при любом контенте.
Три кита: grow, shrink, basis
Когда контейнер становится display: flex, каждый дочерний элемент получает три параметра:
| Свойство | Дефолт | Что делает |
flex-grow | 0 | множитель, как делить лишнее свободное место |
flex-shrink | 1 | множитель, как отдавать место при нехватке |
flex-basis | auto | стартовый размер по главной оси до раздачи/сжатия |
Сначала браузер расставляет элементы по их flex-basis. Затем смотрит: место осталось — раздаёт его пропорционально flex-grow; места не хватает — забирает пропорционально flex-shrink. Дефолт 0 1 auto означает «не расти, но при тесноте — сжиматься, стартуя от своего естественного размера».
Шорткат flex и его пресеты
Писать три свойства руками не нужно — есть flex. Важно знать его типовые формы, потому что одно число разворачивается не очевидно:
.item { flex: 1; } /* = 1 1 0 — равные колонки от нулевого базиса */
.item { flex: auto; } /* = 1 1 auto — растут и сжимаются от своего размера */
.item { flex: none; } /* = 0 0 auto — жёсткий, не растёт и не сжимается */
.item { flex: 0 1 200px; }/* стартует с 200px, сжимается, но не растёт */
Ключевая ловушка: flex: 1 ставит flex-basis: 0, а не auto. Поэтому три элемента с flex: 1 станут идеально равными по ширине, даже если их контент разной длины — стартовый размер обнулён, и всё место делится поровну. А вот flex: auto учитывает размер содержимого: блок с длинным текстом получится шире.
/* три равные колонки независимо от длины текста */
.row { display: flex; gap: 16px; }
.row > * { flex: 1; } /* 1 1 0 — равная ширина */
flex-basis против width
По главной оси flex-basis побеждает width: если задан и тот, и другой, базисом считается flex-basis (кроме flex-basis: auto, который как раз и означает «возьми width или размер контента»). Поэтому flex: 1 (с basis 0) делает заданную width бессмысленной — это частый сюрприз.
align-self: один элемент против всех
align-items на контейнере задаёт выравнивание по поперечной оси для всех; align-self на конкретном элементе переопределяет его лично. Удобно, например, прижать одну иконку к низу, когда остальные центрированы:
.toolbar {
display: flex;
align-items: center; /* все по центру */
gap: 12px;
}
.toolbar .logo { align-self: flex-start; } /* логотип — к верхнему краю */
Перенос строк: flex-wrap и gap
По умолчанию flex-элементы стоят в одну строку и сжимаются как угодно, лишь бы поместиться (flex-wrap: nowrap). Чтобы они переносились, нужен flex-wrap: wrap. В сочетании с flex-basis и flex-grow это даёт «резиновую» сетку:
.tags {
display: flex;
flex-wrap: wrap;
gap: 12px; /* зазор и по строкам, и по столбцам */
}
.tags > * {
flex: 1 1 160px; /* не уже 160px, дальше растут и переносятся */
}
Свойство gap в Flexbox задаёт зазоры сразу по обеим осям и давно поддерживается во всех актуальных браузерах — это лучше старых «костылей» с отрицательными margin. При flex-wrap: wrap распределением строк по поперечной оси управляет align-content (не путать с align-items, который выравнивает элементы внутри строки).
Как это работает под капотом
Раскладка flex-строки — это решение задачи о делении длины. Браузер берёт «гипотетический» размер каждого элемента (его flex-basis, разрешённый из width/контента) и считает суммарную свободную длину контейнера. Если свободная длина положительна, она раздаётся: каждый элемент получает прибавку, пропорциональную своему flex-grow относительно суммы всех grow. Если отрицательна (элементы не влезают), сжатие распределяется пропорционально произведению flex-shrink × flex-basis — то есть крупные элементы с ненулевым shrink отдают больше. Затем к каждому элементу применяются ограничения min-width/max-width (и min-height/max-height на колоночной оси). И вот тут вступает главный подводный камень: у flex-элементов min-width по умолчанию равен auto, а не 0. Это значит, что элемент не сожмётся уже своего минимального контента — длинного слова, картинки, pre — даже если flex-shrink положителен. Именно поэтому контент иногда «прорывает» контейнер.
Частые ошибки
min-width: auto ломает сжатие и переполняет контейнер. Самая частая боль Flexbox: длинный текст или <pre> внутри flex-элемента не даёт ему сжаться, появляется прокрутка. Решение — явно разрешить сжатие:
.flex-item {
min-width: 0; /* разрешаем сжаться уже контента */
overflow: hidden;
}
Думают, что flex: 1 уважает width. flex: 1 = 1 1 0, базис обнулён, поэтому заданная width игнорируется. Нужна стартовая ширина — пишите flex: 1 1 240px или flex: 0 1 240px.
Путают align-items и align-content. При flex-wrap: wrap за расположение целых строк отвечает align-content; align-items выравнивает элементы внутри одной строки. На однострочном контейнере align-content вообще ни на что не влияет.
Забывают про flex-wrap. Без flex-wrap: wrap элементы не переносятся, а бесконечно сжимаются — и упираются как раз в min-width: auto.
Итоги
flex: grow shrink basis— три числа: как расти, как сжиматься, от какого размера стартовать.flex: 1=1 1 0(равные колонки, width игнорируется);flex: auto=1 1 auto(учитывает контент);flex: none=0 0 auto(жёсткий).- По главной оси
flex-basisважнееwidth;align-selfпереопределяетalign-itemsдля одного элемента. flex-wrap: wrapвключает перенос;gapзадаёт зазоры по обеим осям без отрицательных margin.- Дефолтный
min-width: autoмешает сжатию и переполняет контейнер — лечитсяmin-width: 0.