Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

От автора: вы имеете красивый дизайн, в котором есть красивый большой hero-раздел, за которым следует секция из этих трех столбцов прямо под ним. Как и почти любой другой веб-сайт, над которым вы когда-либо работали.

Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

Вы проходите через hero-раздел и приступаете к работе над разделом из трех столбцов. Пора вспомнить о нашем ненадежном товарище flexbox! Однако, вы пишете display:flex и получаете беспорядок вместо ожидаемых трех одинаковых столбцов.

Это происходит из-за того, что flexbox вычисляет базовый размер элемента. Вы, наверное, читали много учебников о Flexbox, и многие из них (в том числе мои собственные) являются чрезмерно упрощенным примером того, что Flexbox делает без действительного углубления в сложные вещи, которые Flexbox делает для нас, что мы считаем само собой разумеющимся.

Я уверен, что вы видели примеры и учебные пособия, в которых рассматривается что-то вроде того, где три div сжимаются вокруг содержимого, внутри них:

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

Подождите, почему? Flex по умолчанию сжимает объекты — такого не может быть! Верно?

Каким бы классным ни был flexbox, то, что он делает под капотом, на самом деле немного странно, потому что по умолчанию он выполняет две вещи одновременно. Сначала он смотрит на размер содержимого, который мы получили бы, объявив в элементе width:max-content. Но помимо этого, flex-shrink также проделывает некоторую работу по уменьшению размеров элементов, но только при необходимости.

Чтобы понять, что происходит, давайте разберем эти вещи и посмотрим, как они работают вместе.

Погружение в max-content

max-content — довольно удобное свойство в определенных ситуациях, но мы хотим понять, как оно работает в данной конкретной ситуации, когда мы пытаемся получить три равных столбца. Итак, давайте на мгновение отбросим flexbox и посмотрим, как max-content работает сам по себе.

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

Intrinsic может сбить вас с толку, но в основном это означает, что мы позволяем контенту определять ширину, а не устанавливаем заданную ширину явно. Ури Шакед отлично описывает такое поведение, говоря: «браузер делает вид, что у него бесконечное пространство, и помещает весь текст в одну строку, измеряя ее ширину».

Итак, нижняя строка max-content позволяет самому контенту определять ширину элемента. Если у вас есть абзац, ширина этого абзаца будет соответствовать тексту внутри него без разрывов строки. Если это достаточно длинный абзац, вы получите горизонтальную прокрутку.

Давайте вернемся к этому черезмерно упрощенному примеру трех элементов блочного уровня, которые сжимаются и размещаются рядом друг с другом. Этого происходит потому, что размер этих элементов равен их заявленной ширине max-content. Ширина блочного элемента почти такая же, как и общая ширина объединенного контента внутри каждого элемента.

Взгляните на эти элементы без flexbox, но вместо этого со свойством width:max-content:

Итак, когда текстов очень мало, сжимает элементы внутренняя функция max-content, а не flex-shrink. Конечно, flexbox также имеет поведение по умолчанию flex-direction:row, которое превращает flex-элементы в столбцы, помещая их рядом друг с другом. Вот еще один пример, но с выделенным свободным пространством.

Добавление flex-shrink

Итак, мы видим, что объявление display:flex подталкивает внутренний размер max-content к flex-элементам. Эти элементы хотят быть такими же большими, как и их содержание. Но есть и другое значение по умолчанию, которое есть в flex-shrink.

flex-shrink в основном смотрит на все flex-элементы в контейнере для того чтобы убедится, что они не переполняют родительский элемент. Если все элементы могут поместиться рядом друг с другом, не переполняя flex-контейнер, то flex-shrink вообще ничего не будет делать… работа уже выполнена.

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

Вот почему по умолчанию для flex-shrink установлено значение 1. Без него все очень быстро запуталось бы.

Вот почему столбцы не равны

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

Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

Используя DevTools Firefox (у него есть несколько уникальных визуализаций flexbox, которых нет у других браузерах), мы можем увидеть, как flexbox вычисляет значения.

Для простоты, давайте поработаем с некоторыми круглыми числами. Мы можем сделать это, объявив ширину наших flex-элементов. Когда мы объявляем ширину flex-элемента, мы выбрасываем его внутренний размер за пределы окна, поскольку вместо этого мы объявили явное значение. Это значительно упрощает понимание того, что происходит на самом деле.

В приведенном ниже CodePen у нас есть родительский элемент, представляющий собой контейнер (display:flex) шириной 600px. Я удалил все, что могло повлиять на цифры, так что нет gap или padding. Я также отключил border, чтобы мы могли легко визуализировать результат.

Первый и третий flex-элементы имеют width:300px а средний — width:600px. Если мы сложим все это, всего получится 1200px. Это больше, чем доступно в родительском элементе 600px, поэтому начинает работать flex-shrink.

Flex-shrink это соотношение. Если все элементы имеют одинаковый flex-shrink (по умолчанию равный 1), они сжимаются с одинаковым соотношением. Это не означает, что все они сжимаются до одного и того же размера или на одинаковую величину, но все они сжимаются с одинаковым соотношением.

Если мы вернемся в Firefox DevTools, мы увидим базовый размер flex-shrinkа и окончательный размер. В данном случае два 300px элемента сейчас имеют ширину 150px, а один 600px — сейчас равен 300px.

Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

Если мы сложим базовые размеры всех трех flex-элементов (фактическую ширину, которую мы для них объявили), общая сумма будет равна 1200px. Ширина нашего flex-контейнера равна 600px. Если разделить все на 2, все сходится! Все элементы уменьшаются с одинаковым соотношением, и их ширина делится на 2.

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

Уравнивание столбцов

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

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

Метод 1: Использование flex:1

Один из способов добиться того, чтобы все flex-элементы имели одинаковый базовый размер, — это объявить для всех flex:1:

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

Средний столбец здесь больше двух других. Так почему же это не сработало? Виной всему свойство компонента padding в среднем столбце.

И, может быть, вы скажете, что глупо добавлять отступы к одному из элементов, а не к другим, или что мы можем вкладывать элементы (мы еще вернемся к этому). Однако, на мой взгляд, у меня должен быть макет, который будет работать независимо от того, какой контент мы в него помещаем.

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

Я не собираюсь углубляться в flex-basis в данной статье. На данный момент мы можем думать об этом как о width, для упрощения, поскольку мы не работаем с flex-direction.

Мы уже видели, как работает flex-shrink. Flex-grow наоборот. Если размер всех гибких элементов вдоль главной оси меньше, чем у родительского элемента, они могут увеличиваться, чтобы заполнить это пространство. Итак, по умолчанию flex-элементы:

не растут;

если они переполняют родительский элемент, они могут сжиматся;

их ширина действует как max-content.

Собирая все вместе, flex по умолчанию:

Самое интересное в flex сокращении — это то, что вы можете опускать значения. Так что, когда мы объявляем flex:1, это установка первого значения, flex-grow равным 1.

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

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

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

Но если у нас есть три div с неравной шириной — как те, с которых мы начали, — добавление flex-grow к ним вообще ничего не даст. Они не будут расти, потому что они уже занимают все доступное пространство – и на самом деле, их нужно сжать с помощью flex-shrink, чтобы они уместились!

Но, как мне сказали, настройка flex:1 может работать для создания одинаковых столбцов. Ну вроде как мы видели выше! В простых ситуациях это действительно работает, как вы можете видеть ниже.

Когда мы объявляем, что flex:1 это работает, потому что это не только устанавливает значение flex-grow равным 1, но также изменяет значение flex-basis!

Настройка flex:1 устанавливает flex-basis значение 0%. Это перезаписывает тот внутренний размер, который у нас был до того, как мы вели max-content. Все наши flex- элементы теперь хотят иметь базовый размер 0!

Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

Глядя на расширенное представление flex:1 в DevTools, мы видим, что значение flex-basis изменилось на 0

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

Одинаковые столбцы с помощью Flexbox: это сложнее, чем вы думаете

DevTools FireFox показывает, что элемент flex:1 имеет размер содержимого 0 и увеличивается, чтобы заполнить доступное пространство.

Как и в предыдущем примере сжатия, мы берем доступное пространство и позволяем всем flex-элементам расти с одинаковым соотношением. Поскольку все они имеют базовую ширину 0, рост с одинаковым соотношением означает, что доступное пространство делится между ними поровну, и все элементы имеют одинаковый конечный размер!

Но, как мы видели, это не всегда так …

Причина этого в том, что когда flexbox выполняет все это и распределяет пространство, независимо от того, сжимается ли или растет flex-элемент, он смотрит на размер содержимого элемента. Если вы вернетесь назад к блочной модели, мы имеем размер самого контента, затем padding, border и margin вне этого.

И нет, я не забыл * { box-sizing: border-box; }.

Это одна из странных причуд CSS, но она имеет смысл. Если бы браузер смотрел на ширину этих элементов и включал padding и borders в вычисления, как он мог бы уменьшить их? Либо padding должен будет сжиматься, либо что-то переполнит пространство. Обе эти ситуации ужасны, поэтому вместо того, чтобы смотреть на значение элементов box-size при вычислении их базового размера, он смотрит только на поле содержимого!

Итак, настройка flex:1 вызывает проблему в тех случаях, когда некоторые из элементов содержат borders или padding. Теперь у меня есть три элемента с размером содержимого 0, но у среднего из них есть отступы. Если бы у нас не было этого отступа, у нас была бы та же математика, что и при рассмотрении того, как работает flex-shrink.

Родительский элемент шириной 600px и три flex-элемента шириной 0px. У всех них есть flex-grow:1, поэтому они растут с одинаковым соотношением, и каждый из них становится шириной 200px. Но padding все портит . Вместо этого я получаю три div с размером содержимого 0, но у среднего есть padding:1rem. Это означает, что он имеет размер содержимого 0 плюс отступы 32px.

Мы должны делить поровну 600 — 32 = 568px, а не 600px. Все div хотят расти с одинаковым соотношением, поэтому 568 / 3 = 189.3333px.

И вот что происходит!

Но … помните, это размер их содержимого, а не общая ширина! Остается два div с шириной 189.333px и еще один div с шириной 189.333px + 32 = 221.333px. Это довольно существенная разница!

Способ 2: flex-basis: 100%

Я всегда поступал так:

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

Все элементы пытаются быть шириной 100%, что означает, что все они хотят соответствовать ширине родительского элемента, которая, опять же, является 600px(а в нормальных обстоятельствах часто намного больше, что еще больше уменьшает заметную разницу).

Дело в том, что в вычисленные значения 100% включаются отступы (из-за * { box-size: border-box; }которые, я предполагаю, что все используют). Итак, внешние блоки div имеют размер содержимого 600px, тогда как средний div имеет размер содержимого 600 — 32 = 568px.

Как равномерно распределить пространство, учитывая что делить нужно 1768px на 600px а не 1800px. Кроме того, как мы видели ранее, flex-элементы сжимаются не в одинаковой степени, а с одинаковсоотношением! Таким образом, элемент с padding сжимается немного меньше, чем другие.

Это приводит к тому, что .card имеет ширину в 214.483px в то время как ширина других равна 192.75px. Опять же, это приводит к неравным значениям ширины, хотя разница меньше, чем мы видели с решением при flex:1.

Почему CSS Grid – это лучший выбор

Хотя все это немного расстраивает (и даже сбивает с толку), все это происходит по уважительной причине. Если поля, отступы или границы будут менять размер при использовании flex, это будет кошмар.

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

Я долго думал, что легче взять и начать использовать flexbox, чем grid, но grid дает вам больше контроля в долгосрочной перспективе, но это намного сложнее понять. Однако, недавно я изменил свое мнение, и я думаю, что сетка не только дает нам лучший контроль в подобных ситуациях, но и на самом деле более интуитивно понятна.

Обычно, когда мы используем сетку, мы явно объявляем наши столбцы с помощью grid-template-columns. Однако мы не обязаны этого делать. Мы можем заставить столбцы вести себя так же, как flexbox, используя grid-auto-flow:column.

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

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

Здесь интересно то, как работают юниты fr. В спецификации они буквально называются «flex units», и они работают так же, как и flexbox при разделении пространства. Большая разница: они смотрят на другие столбцы, чтобы определить, сколько у них места, не обращая внимания на содержимое внутри столбцов.

Это настоящая магия. Объявляя grid-auto-columns:1fr, мы, по сути, говорим: «По умолчанию все мои столбцы должны иметь одинаковую ширину», чего мы и добивались с самого начала!

А как насчет маленьких экранов?

Что мне нравится в этом подходе, так это то, что мы можем сделать его очень простым:

И вот так просто – все работает отлично. Объявив display:grid с самого начала, мы можем включить, gap чтобы сохранить равный интервал, независимо от того, являются ли дочерние элементы строками или столбцами.

Я также считаю, что это намного более интуитивно понятно, чем изменение flex-direction в медиа-запросе для получения аналогичного результата. Как бы мне ни нравился flexbox (и я все еще думаю, что у него отличные варианты использования), тот факт, что объявление flex-direction:column создает строки, наоборот, на первый взгляд немного противоречит интуиции.

И, конечно же, если вы предпочитаете код без медиа-запросов, ничто не мешает вам перейти на следующий уровень с помощью auto-fit, что было бы похоже на настройку чего-то вроди flex-wrap (хотя и не совсем то же самое):

Заставляем код работать с Flexbox

Я понимаю, что мы можем обойти это с помощью flexbox, вложив в него элемент padding. Мы делали так с тех пор, как начали создавать макеты с использованием float, и Bootstrap действительно внедрил этот тип шаблона проектирования.

И в этом нет ничего плохого. Оно работает! Но float тоже работает, а мы перестали использовать их для макетов, так как в последние годы были выпущены лучшие решения.

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

Конечно, Flexbox все еще актуален

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

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

Однако в других ситуациях, когда вы обнаруживаете, что боретесь с проблемами flexbox, возможно, вы могли бы вместо этого обратиться к сетке, даже если это не типичная «2d» сетка.

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

Автор: Kevin Powell

Источник: css-tricks.com

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен

Метки:

Похожие статьи:

Комментарии Вконтакте:

Комментарии запрещены.