Сайт вебмастера

Адаптивная сетка на flexbox

22-01-2021Время чтения ~ 8 мин.Berry CSS 4930

Сделать сетку на flex просто. Намного сложнее добиться её хорошей адаптивности. На сегодняшний день используется несколько разных подходов к этой задаче. Самый распространённый — это использование классов ширины в media-условиях. Здесь мы указываем разную ширину в зависимости от ширины экрана.

У такого способа есть несколько проблем, главная из которых — здесь не учитывается наполненность каждой ячейки. Может получится так, что ячейка с объёмным содержимым будет занимать меньше ширины. Чтобы сделать сетку, которая будет сама подстраиваться под своё содержимое, приходится отказаться от фиксированной ширины. Но, для начала рассмотрим вариант, где используется «стандартный» вариант с фиксированной шириной, чтобы понять суть проблемы.

Все примеры я привожу в классах Berry CSS. Примеры кода можно загрузить в Builder. Здесь я не буду приводить рабочие примеры, поскольку для тестирования нужно менять ширину браузера.

Итак, вначале вариант с фиксированной шириной.

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="w50 w100-tablet bg-green100 pad10">
		text
	</div>
	
	<div class="w30 w50-tablet bg-green200 pad10">
		text
	</div>
	
	<div class="w20 w50-tablet bg-green300 pad10">
		text
	</div>
</div></div>

Я использую layout-контейнер, чтобы выровнять блоки по центру браузера. Представьте себе, что этот блок — компонент шапки. Класс flex задаёт flex-сетку, а flex-wrap-tablet разрешает перенос ячеек на планшете.

Ячейки имеют разную ширину — это w-классы. Первая ячейка 50%, вторая 30%, третья 20%.

На планшете размеры меняются: 100%, 50% и 50%. Остальные классы для оформления ячеек.

Данный подход уже стал традиционным: фиксированная ширина для разных media-условий. В чём же здесь проблема?

Представьте себе, что содержимое третьей ячейки окажется больше, чем первой. Например там текст или форма. Очевидно, что нам придётся менять класс ширины, пытаясь подобрать его так, чтобы обеспечить корректное отображение.

Теперь, заметьте, что ширина в процентах рассчитывается от физической ширины контейнера. На десктопе это 1200px. Следующая media-точка — 768px (планшет, tablet). То есть ширина контейнера может уменьшится более чем на 400px, но пропорция в процентах сохраняется. Возникает ситуация, когда последней ячейки физически не хватит места, чтобы корректно вывести форму скажем на 800px, но при этом на 1200px она будет выглядеть прекрасно.

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

Во flexbox для этого используются два свойства: grow и shrink.

Свойство grow

Это свойство часто называется «жадность», то есть задаёт то, насколько ячейка может увеличиться при необходимости.

Посмотрите как работает следующий пример:

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-grow3 bg-green100 pad10">
		text
	</div>
	
	<div class="flex-grow2 bg-green200 pad10">
		text
	</div>
	
	<div class="flex-grow1 bg-green300 pad10">
		text
	</div>
</div></div>

Первая ячейка имеет «жадность» 3, а значит она будет иметь самую большую ширину. Это очевидное и ожидаемое поведение, но на самом деле это не так. Посмотрите этот же самый пример, только с содержимым, где последняя ячейка имеет текста в 3 раза больше, а вторая в 2 раза больше, чем первая.

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-grow3 bg-green100 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 bg-green200 pad10">
		text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow1 bg-green300 pad10">
		text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text 
	</div>
</div></div>

Круто, да? Мы получили полностью противоположное поведение сетки — теперь третья ячейка самая широкая.

Это происходит из-за того, что свойство grow работает только с «пустым» местом. В первом примере, текста мало, поэтому там много свободного пространства и с помощью grow мы задаём приоритет на его получение.

Во втором же случае, текста много и пустого пространства не остаётся, а значит grow игнорируется браузером и ячейки начинают занимать ширину в зависимости от своего содержимого.

Таким образом, при построении адаптивной сетки, нужно учитывать фактор заполняемости ячеек. Если там будет много свободного пространства, то мы можем использовать grow. В противном случае — этот вариант просто не сработает.

Свойство shrink

Свойство shrink задаёт степень сжатия ячейки, когда места не хватает.

Следующий пример демонстрирует эту возможность:

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-shrink1 bg-green100 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-shrink3 bg-green200 pad10">
		text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-shrink4 bg-green300 pad10">
		text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text 
	</div>
</div></div>

Первая ячейки имеет самый низкий уровень сжатия (1 — строго говоря его можно не указывать), второй сжатие — 3, а третья ячейка — 4. Результат ожидаем — третья ячейка самая узкая, хотя имеет наибольший контент.

Меняя классы flex-shrinkX (1..5) можно получить разное поведение ячеек. При этом попробуйте менять объём содержимого ячеек. Браузер будет стараться подбирать ширину ячеек, чтобы уменьшить занимаемую высоту всей сетки. Попробуйте указать у второй и третьей ячейках одинаковые классы, например flex-shrink4. Это хорошо демонстрирует то, как браузер старается наиболее оптимально разместить всё содержимое.

Объединяем

Итак у нас есть две пограничные ситуации:

  • Первая — когда много свободного места и работает grow
  • Второе — когда его нет и работает shrink

В простом виде это будет так:

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-grow2 flex-shrink2 bg-green100 pad10">
		text
	</div>
	
	<div class="flex-grow2 flex-shrink2 bg-green200 pad10">
		text
	</div>
	
	<div class="flex-grow2 flex-shrink2 bg-green300 pad10">
		text
	</div>
</div></div>

Когда текста мало, то ячейки будут растягиваться за счёт свободного места. Классы flex-growX (1..5) задают степень растяжения ячейки.

Когда же текста много, то начинают работать классы flex-shrinkX (1..5), которые задают степень сжатия ячейки.

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

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

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-grow1 flex-shrink1 bg-green100 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 bg-green200 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 bg-green300 pad10">
		text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text 
	</div>
</div></div>

Ограничение ширины

В последнем примере первая ячейка имеет приоритет (flex-grow1), а вторая окажется самой маленькой, поскольку у неё недостаточно контента (по сравнению с последней). В итоге на небольшой ширине (например 800px), вторая ячейка сжимается слишком уж сильно. Это очень напоминает ситуацию с фиксированной шириной. Поэтому нужен способ сохранить текущую «резиновую адаптивность», но гарантировать минимальный размер ячейки.

И делается это очень просто: мы просто используем классы -min, которые задают минимальную ширину.

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-grow1 flex-shrink1 bg-green100 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 w200px-min bg-green200 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 w20-min bg-green300 pad10">
		text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text 
	</div>
</div></div>

Класс w200px-min задаёт минимальную ширину в 200px, что гарантирует корректное отображение контента. Если содержимое тоже «резиновое», то можно использовать и «процентные» классы, например w20-min (минимум 20%).

У ячейки, которая имеет приоритет (у нас это первая с flex-grow1 flex-shrink1 — их можно не указывать / я их показываю для «чистоты» объяснения) можно явно задавать ширину, как и в фиксированном варианте. Например, чтобы ширина была не менее 50% можно использовать класс w50-min. Или наоборот, если хотим ограничить ширину, то используем классы -max, например w50-max.

Малые экраны

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

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

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-grow1 flex-shrink1 bg-green100 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 bg-green200 pad10">
		text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 bg-green300 pad10">
		text text text text text text text
	</div>
</div></div>

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

<div class="layout-center-wrap mar10-tb"><div class="layout-wrap flex flex-wrap-tablet">
	<div class="flex-grow1 flex-shrink1 bg-green100 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 w50-tablet w100-phone bg-green200 pad10">
		text text text text text text text text text text text text text text text text text text text text 
	</div>
	
	<div class="flex-grow2 flex-shrink5 w50-tablet w100-phone bg-green300 pad10">
		text text text text text text text text text text text text text text text text text text text text
	</div>
</div></div>

Класс w50-tablet задаёт 50% ширину для планшета, а w100-phone — 100% для телефона.

Итого

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

  • Когда есть свободное пространство, используем grow.
  • Когда контента достаточно, то задаём степень сжатия с помощью shrink.
  • Для ограничения ширины используются классы -min и -max.
  • Для малых экранов можно использовать media-классы.
Похожие записи