CSS функция min() и адаптивность сайта

alexei25/03/2025 - 09:02
CSS функция min() и адаптивность сайта

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

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

Что было во всем этом интересного конкретно для нас, так это то, как в этой статье демонстрируется сложность определения размера разных вещей. В CSS мы ограничены абсолютными и относительными единицами измерения, поэтому мы либо придерживаемся определенного размера (например, px), либо вычисляем размер на основе размера, объявленного для другого элемента (например, %, em, rem, vw, vh и так далее). И то, и другое сопряжено с компромиссами, так что не похоже, что существует "правильный" способ действий - все зависит от контекста элемента, - и уклон в какую-либо одну сторону этого не исправит.

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

Но для начала давайте разберемся в деталях.

Немного о CSS функции min()

CSS функция min() принимает два значения и применяет наименьшее из них, в зависимости от контекста элемента. Например, нам нужно, чтобы элемент был такой же ширины, как 50% от контейнера, в котором он находится. И если 50% больше, чем, скажем, 200px, то вместо этого ограничим ширину.

Таким образом, функция min() похожа на контейнерные запросы в том смысле, что она знает, сколько свободного места есть в контейнере. Но отличие заключается в том, что min() не запрашивает размеры своего контейнера для вычисления окончательного значения. Мы задаём ей две допустимых значения, и она определяет, какое из них лучше подходит для данного контекста. Это делает функцию min() (а также max()) полезным инструментом для адаптивных макетов, которые подстраиваются под размер области просмотра. Она использует условную логику для определения "наилучшего" соответствия, а значит, может помочь адаптировать макеты без необходимости использовать медиазапросы CSS.


.element {
  width: min(200px, 50%);
}

/* Приблизительно то же самое: */
.element {
  width: 200px;

  @media (min-width: 600px) {
    width: 50%;
  }
}

Разница между min() и @media в этом примере заключается в том, что в медиазапросе мы указываем браузеру установить ширину элемента 50% при размере 600px. При использовании min() всё настраивается автоматически в зависимости от доступного пространства, независимо от размера области просмотра.

Используя функцию min(), мы воспринимаем ее, как способную принимать разумные решения на основе контекста. Нам не нужно думать или производить вычисления, чтобы определить, какое значение используется. Однако простого использования функции min() в сочетании с любыми единицами измерения CSS недостаточно. Например, относительные единицы измерения лучше подходят для адаптивности, чем абсолютные. Можно даже рассматривать функцию min() как определение максимального значения, которое никогда не опускается ниже первого значения, но и не превышает второе значение.

Ранее мы упоминали, что с функцией min() можно использовать любой тип единиц измерения. Давайте воспользуемся этим и будем опираться на тип единиц измерения, чтобы посмотреть, как функция min() ведёт себя при использовании исключительно для адаптивного макета. В частности, мы будем использовать единицы измерения области просмотра, так как они напрямую связаны с размером этой самой области просмотра.

И здесь есть различные варианты единиц измерения области просмотра. Мы можем использовать ширину (vw) и высоту (vh) области просмотра. Также есть vmin и vmax, которые немного более интеллектуальны, так как они оценивают ширину и высоту элемента и применяют меньшую (vmin) или большую (vmax) величину. Таким образом, если мы объявляем 100vmax для элемента, ширина которого 500px, а высота 250px, то единица измерения вычисляется как 500px.

Сформулируем задачи нашего эксперимента следующим образом. Что произойдёт, если для создания адаптивного макета мы откажемся от медиазапросов в пользу функции min() и для этого будем опираться на единицы измерения области просмотра? Начнем реализацию постепенно.

Изменение размера шрифта

Существуют различные подходы к адаптивной вёрстке. При этом медиазапросы быстро становятся "старомодным" способом:


p { font-size: 1.1rem; }

@media (min-width: 1200px) {
  p { font-size: 1.2rem; }
}

@media (max-width: 350px) {
  p { font-size: 0.9rem; }
}

Конечно, это работает, но что будет, если пользователь использует монитор с разрешением 4K? Или складной телефон? Есть и другие проверенные подходы; на самом деле, использование функции clamp() это самый распространённый вариант. Но мы полностью полагаемся на функцию min(). И как оказалось, нужна всего одна строка кода, чтобы избавиться от всех этих медиазапросов и значительно сократить код:


p { font-size: min(6vmin, calc(1rem + 0.23vmax)); }

Пройдемся по всем этим значениям...

  1. 6vmin по сути, это 6% от ширины или высоты браузера, в зависимости от того, что меньше. Это позволяет уменьшать размер шрифта настолько, насколько необходимо для небольших контекстов.
  2. В выражении calc(1rem + 0.23vmax) значение 1rem - это базовый размер шрифта, а 0.23vmax - это небольшая часть ширины или высоты области просмотра, в зависимости от того, что больше.
  3. Функция calc() складывает эти два значения. Поскольку 0.23vmax вычисляется по-разному в зависимости от того, какая сторона области просмотра является наибольшей, это важно, когда речь идёт о масштабировании размера шрифта между двумя аргументами. Мы изменили её так, чтобы она постепенно масштабировалась в ту или иную сторону, а не увеличивалась скачком по мере увеличения размера области просмотра.
  4. Наконец, min() возвращает наименьшее значение, подходящее для размера шрифта текущего размера экрана.

И если говорить о том, насколько гибок подход с функцией min(), то он может ограничивать увеличение текста. Например, мы можем установить максимальное значение font-size равное 2rem в качестве третьего параметра функции:


p { font-size: min(6vmin, calc(1rem + 0.23vmax), 2rem); }

Конечно, это не панацея. Можно даже сказать, что лучше всего этот подход использовать для основного текста, например, для абзацев. Возможно, для заголовков нам придётся немного изменить настройки, например, для <h1> записать так:


h1 { font-size: min(7.5vmin, calc(2rem + 1.2vmax)); }

Мы увеличили минимальный размер с 6vmin до 7.5vmin, чтобы заголовок был больше основного текста при любом размере области просмотра. Кроме того, в calc() базовый размер теперь составляет 2rem, что меньше, чем в стандартных стилях UA для элемента <h1>. На этот раз мы используем 1.2vmax в качестве множителя, то есть он увеличивается больше, чем основной текст, который умножается на меньшее значение 0.23vmax.

Вы всегда можете изменить эти значения и посмотреть, что лучше подходит для ваших целей. В любом случае, font-size в данном эксперименте является полностью адаптивным и полностью базируется на функции min(), что соответствует нашему самоограничению.

Отступы и поля

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

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

Чтобы получить более эффективный результат, в функции min() можно комбинировать относительные и абсолютные единицы измерения. Давайте на этот раз объединим vmin с px:


div { margin: min(10vmin, 30px); }

Значение 10vmin при просмотре на маленьком экране, скорее всего, будет меньше, чем 30px. Поэтому на этот раз мы позволяем отступу уменьшиться динамически. По мере увеличения размера окна просмотра, когда значение 10vmin становится больше 30px, функция min() ограничивает значение до 30px.

Также обратите внимание, что на этот раз мы не используем функцию calc(). Поля не должны бесконечно увеличиваться вместе с размером экрана, так как слишком большое расстояние между элементами на больших экранах обычно выглядит странно. Эта концепция также отлично подходит для внутренних отступов. Но нам не обязательно уходить в нее. Вместо этого лучше использовать единые единицы измерения, предпочтительно em, так как единицы em зависят от размера шрифта. В результате мы можем "распространить" работу, которую функция min() проделывает над font-size, на свойства margin и padding.


.card-info {
  font-size: min(6vmin, calc(1rem + 0.12vmax));
  padding: 1.2em;
}

Теперь внутренние отступы padding масштабируются по размеру шрифта font-size, который вырабатывается функцией min().

Ширина

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

Но мы все равно обратимся к функции min().

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


.container { width: min(100%, 650px); }

Всё становится интереснее с шириной текста. Когда ширина текстового блока слишком большая, читать текст становится неудобно. Существуют конкурирующие теории о том, сколько символов в строке лучше всего подходит для оптимального восприятия текста. Для аргументации предположим, что это число должно составлять от 50 до 75 символов. Другими словами, в строке должно быть не более 75 символов, и мы можем сделать это с помощью единиц измерения ch, которые основываются на размере символа "0" в используемом шрифте.


p {
  width: min(100%, 75ch);
}

Этот код гласит: занимай в ширину сколько нужно, но не больше 75 символов.

Рецепты изменения размеров нам основе min()

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


:root {
  --font-size-6x: min(7.5vmin, calc(2rem + 1.2vmax));
  --font-size-5x: min(6.5vmin, calc(1.1rem + 1.2vmax));
  --font-size-4x: min(4vmin, calc(0.8rem + 1.2vmax));
  --font-size-3x: min(6vmin, calc(1rem + 0.12vmax));
  --font-size-2x: min(4vmin, calc(0.85rem + 0.12vmax));
  --font-size-1x: min(2vmin, calc(0.65rem + 0.12vmax));
  --width-2x: min(100vw, 1300px);
  --width-1x: min(100%, 1200px);
  --gap-3x: min(5vmin, 1.5rem);
  --gap-2x: min(4.5vmin, 1rem);
  --size-10x: min(15vmin, 5.5rem);
  --size-9x: min(10vmin, 5rem);
  --size-8x: min(10vmin, 4rem);
  --size-7x: min(10vmin, 3rem);
  --size-6x: min(8.5vmin, 2.5rem);
  --size-5x: min(8vmin, 2rem);
  --size-4x: min(8vmin, 1.5rem);
  --size-3x: min(7vmin, 1rem);
  --size-2x: min(5vmin, 1rem);
  --size-1x: min(2.5vmin, 0.5rem);
}

Именно так мы подошли к нашему эксперименту, потому что это помогает понять, к чему стремиться в той или иной ситуации:


h1 { font-size: var(--font-size-6x); }

.container {
  width: var(--width-2x);
  margin: var(--size-2x);
}

.card-grid { gap: var(--gap-3x); }

И вот, что мы имеем! У нас есть заголовок, который идеально масштабируется; контейнер, который подстраивается под размер экрана и никогда не становится слишком широким; и сетка с динамическим расстоянием между элементами. И всё это без единого медиазапроса. Переменные --size- в нашем списке являются наиболее универсальными, так как их можно использовать для свойств, требующих масштабирования, например, отступов, полей и так далее.

Финальный результат. Снова.

Вот что у нас получилось:

Итак, является ли CSS функция min() универсальным средством создания адаптивности? Безусловно, нет. Как и диета, состоящая исключительно из контейнерных единиц. В том смысле, что здорово, что таким образом мы можем масштабировать всю веб-страницу, но веб никогда не будет универсальным.

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