Существует множество руководств, в которых рассматриваются общие вопросы работы с CSS Grid, с механизмом, позволяющим создавать сеточные макеты. Я и сам немало об этом писал. Но я обратил внимание на то, что у многих разработчиков возникают сложности с использованием CSS-функции minmax(). Пожалуй, дело тут в том, что большинство существующих публикаций на эту тему либо не вдаются в детали, либо не включают в себя достаточного количества пояснений и примеров из реального мира. А minmax() — это очень мощная и полезная функция. Именно по этой причине я и решил написать данную статью. Это — нечто вроде «полного руководства по minmax()», задача которого — дать читателям то, чего не дают им другие публикации на эту тему.



Здесь мы в подробностях обсудим возможности функции minmax() в применении к сеточным макетам, поговорим о том, как, когда и почему ей стоит пользоваться. Тот, кто проработает эту статью, сможет полноценно и со знанием дела применять эту функцию в своих проектах.

Общие вопросы использования minmax() в Grid-макетах


В спецификации CSS о функции minmax(min, max) сказано, что она определяет диапазон размеров, которые больше или равны min и меньше или равны max.

Эту функцию можно использовать в качестве значения, задающего параметры столбцов или строк Grid-макета. Рассмотрим простой пример:

.o-grid {
  display: grid;
  grid-template-columns: minmax(200px, 500px) 1fr 1fr;
  grid-gap: 1rem;
}

Вот как это будет выглядеть на схеме.


Результат применения функции minmax() при создании Grid-макета

Проанализируем этот макет:

  1. Здесь имеется сетка с тремя столбцами.
  2. Ширина первого столбца задана как minmax(200px, 500px). Минимальная ширина этого столбца составляет 200px, максимальная — 500px.
  3. Два других столбца имеют ширину 1fr. Это означает, что они займут оставшееся свободное пространство.

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


Горизонтальная полоса прокрутки

В результате оказывается, что функция minmax(), сама по себе, не поддерживает создание отзывчивых макетов. А это значит, что поведением страницы приложения на экранах разной ширины нам нужно управлять самостоятельно. Ниже, в разделе практических примеров, мы к этому вернёмся.

Проверка правильности использования функции minmax()


Если значение min, переданное функции minmax(min, max), больше значения max, то значение max будет проигнорировано. В результате в конструкции minmax(min, max) реально использоваться будет лишь минимальное значение.


Если значение min больше значения max — значение max игнорируется

Кроме того, важно помнить о том, что значение вроде 1fr нельзя использовать в качестве значения min. Оно может быть использовано только в роли значения max. При этом подобная ситуация хуже, чем та, когда min больше, чем max! Система проигнорирует всю конструкцию, содержащую подобное объявление.


Нельзя использовать 1fr в качестве значения min

Использование нуля в качестве значения min функции minmax()


Что произойдёт в том случае, если попытаться воспользоваться конструкцией вида minmax(0, 500px)? Пожалуй, вы уже знаете ответ на этот вопрос. Ширина столбца будет, как минимум, нулевой, и при этом она не превысит 500px. То есть — ширина столбца будет меняться в достаточно широких пределах.


Ширина столбца может изменяться в диапазоне от 0 до 500px

Простой сеточный макет


Представим, что нам надо создать сеточный макет, состоящий из 3 колонок. Мы поступим так:

.o-grid {
  display: grid;
  grid-template-columns: minmax(200px, 1fr) minmax(200px, 1fr) minmax(200px, 1fr);
  grid-gap: 1rem;
}

Колонки будут иметь минимальную ширину в 200px. Вот как они будут выглядеть.


Макет, минимальная ширина столбцов которого равна 200px

Избежать троекратного повторения конструкции minmax(200px, 1fr) можно, воспользовавшись функцией repeat():

.o-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(200px, 1fr));
  grid-gap: 1rem;
}

Вышеприведённые стили вполне работоспособны, но пользоваться подобными конструкциями не рекомендуется. Поговорим о том, почему это так.

?Ручное изменение количества столбцов


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

?Проблема появления горизонтальной полосы прокрутки


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


Горизонтальная полоса прокрутки

Как справиться с этой проблемой? Надо сообщить браузеру о том, что количество столбцов должно быть уменьшено в том случае, если ширины области просмотра недостаточно для вывода всего макета.

При разработке Flexbox-макетов это делается путём добавления свойства flex-wrap: wrap к родительскому элементу макета:

.parent {
  display: flex;
  flex-wrap: wrap;
}


Flexbox-макет, в котором используется свойство flex-wrap: wrap, и макет, в котором это свойство не используется

При проектировании Grid-макетов для достижения подобного эффекта можно воспользоваться ключевыми словами auto-fill и auto-fit.


Сеточный макет, в котором применяются auto-fit/auto-fill, и макет, в котором эти ключевые слова не применяются

Использование ключевых слов auto-fit и auto-fill


Для того чтобы справиться с проблемой жёсткой настройки количества столбцов сеточного макета, мы можем воспользоваться возможностями ключевых слов auto-fit и auto-fill. При этом надо отметить, что между ними существует довольно-таки тонкое различие.

Так, при использовании auto-fit и при наличии свободного места элементы сетки будут растянуты. А применение auto-fill приводит к тому, что элементы растягиваться не будут. В результате дополнительное свободное пространство окажется незанятым, ширина элементов меняться не будет.


Результаты применения ключевых слов auto-fill и auto-fit

При использовании этих ключевых слов может получиться один и тот же результат. Это зависит от ширины области просмотра страницы. Но при определённой ширине области просмотра различия между ними будут уже вполне заметными.

Вот видеозапись, в которой показано поведение макетов, созданных, соответственно, с использованием auto-fill и auto-fit.


Исследование поведения макетов, созданных с использованием ключевых слов auto-fill и auto-fit

?Распределение свободного пространства между столбцами auto-fit-макета


Вот видео, в котором прямоугольник, обведённый пунктирной линией, представляет свободное пространство. Когда, при использовании ключевого слова auto-fit, ширина области просмотра достаточно велика, браузер «свернёт» это свободное пространство и распределит его ширину между элементами сетки.


Распределение свободного пространства между столбцами auto-fit-макета

?Распределение свободного пространства между столбцами auto-fill-макета


Если говорить о применении ключевого слова auto-fill, то тут браузер поступает со свободным пространством иначе. А именно, при увеличении ширины области просмотра ширина элементов не увеличивается. При этом размер свободного пространства (оно, в этом видео, выделено пунктиром) растёт.


Распределение свободного пространства между столбцами auto-fill-макета

Сценарии использования и практические примеры


?Карточки, основанные на сеточном макете



Карточки

Полагаю, что функцию minmax() чаще всего используют для оформления карточек, применяя её при создании элемента-контейнера.

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  grid-gap: 1rem;
}

Когда я начал изучать технологию Grid, я неуютно чувствовал себя, глядя на свойство grid-template-columns, использованное в этом примере. Тут важно обратить внимание на то, как поведёт себя страница в областях просмотра, ширина которых меньше 250px. Дело в том, что в таких ситуациях на экране появится горизонтальная полоса прокрутки.


Появление горизонтальной полосы прокрутки в области просмотра, ширина которой меньше 250px

Решить эту проблему можно двумя способами. Первый заключается в использовании медиа-запросов. В основе этого способа лежит идея, в соответствии с которой grid-template-columns устанавливается в 1fr. А когда ширина области просмотра достаточно велика — применяется minmax():

.wrapper {
  display: grid;
  grid-template-columns: 1fr;
  grid-gap: 1rem;
}

@media (min-width: 300px) {
  .wrapper {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  }
}

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

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
  grid-gap: 1rem;
}

Я использовал тут, в качестве первого значения функции minmax(), функцию сравнения min(). Вот что здесь происходит:

  • Если ширина области просмотра меньше, чем 250px, первым значением, передаваемым minmax(), будет 100% ширины родительского элемента.
  • Если ширина области просмотра будет больше, чем 250px, тогда первым значением minmax() будет 250px.

Тут мы вышли на более удачное решение проблемы и при этом использовали меньший объём CSS-кода. Если вы хотите ближе познакомиться с CSS-функциями сравнения — вот мой материал об этом.

?Использование единицы измерения ch при настройке элемента-контейнера


Мне кажется интересным пример использования функции minmax() при создании макета, предназначенного для отображения материалов статей. В данном примере содержимое статьи центровано по горизонтали.

.wrapper {
  display: grid;
  grid-template-columns: minmax(1rem, 1fr) minmax(auto, 70ch) minmax(1rem, 1fr);
  grid-gap: 1rem;
}

Первый и последний столбцы играют вспомогательную роль, управляя свободным пространством. Нас тут интересует центральный столбец. Обратите внимание на то, что при его настройке использована конструкция вида minmax(auto, 70ch). Это означает, что максимальной шириной данного столбца является ширина, которую занимают 70 символов, выстроенных в одну строку. Это — идеальное количество символов на строку, обеспечивающее комфортное чтение текста.


Макет, предназначенный для вывода текста статьи

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


Статья на экране мобильного устройства

?Проблема, возникающая при необдуманном использовании ключевого слова auto-fit


У того, кто впервые узнал о ключевом слове auto-fit, может возникнуть желание использовать его повсюду. Но тут есть одна проблема, которая проявляется тогда, когда содержимое сетки (например — количество карточек) меняется, а разработчик не контролирует это содержимое.

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-gap: 1rem;
}

Тут описана сетка, используемая для вывода карточек. Когда имеется 4 карточки — макет выглядит замечательно. Но если карточка всего одна — она растянется на всё свободное пространство.


Проблема необдуманного использования auto-fit

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


Вывод карточки с изображением

Полагаю, теперь суть этой проблемы совершенно очевидна. Поэтому предлагаю пользоваться ключевым словом auto-fit ответственно. В описанной ситуации им вполне можно воспользоваться тогда, когда в карточках нет изображений. Если же в карточках имеются изображения — пользоваться auto-fit я не рекомендую.

Пользуетесь ли вы CSS-функцией minmax() в Grid-макетах?