CSS min() — всё, что нужно

Источник: «CSS min() All The Things»
Victor Ayomipo экспериментирует с CSS функцией min(), исследуя её гибкость с помощью различных единиц измерения, чтобы определить, является ли она абсолютной гарантией отзывчивости. Узнайте, какие предостережения он делает против догматических подходов к веб-дизайну, основываясь на своих выводах.

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

See the Pen

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

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

Демонстрация

Не стану заставлять ждать конца, чтобы узнать, как прошёл эксперимент с min():

Поговорим об этом подробнее после того, как разберёмся в деталях.

Немного о min()

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

See the Pen

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

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

/* Близко к этому: */
.element {
width: 200px;

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

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

Когда я использую в CSS 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 вычисляется по-разному в зависимости от того, какой край области просмотра является самым большим, это имеет решающее значение, когда дело доходит до масштабирования font-size между двумя аргументами. Я подправил её так, чтобы она постепенно масштабировалась в ту или иную сторону, а не раздувалась при увеличении размера области просмотра.
  4. Наконец, min() возвращает наименьшее значение, подходящее для font-size при текущем размере экрана.

И если говорить о гибкости 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

Расстояние между элементами — важная часть вёрстки, отзывчивой или нет. Нам нужны margin и padding, чтобы правильно размещать элементы рядом с другими элементами и давать им свободное пространство как внутри, так и снаружи.

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

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

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

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

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

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

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

Ширина

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

Но мы за 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-, объявленные в списке переменных, являются наиболее универсальными, поскольку их можно использовать для свойств, требующих масштабирования, например, для margin, padding и так далее.

Окончательный результат, снова

Я выкладывал видео с результатом, но вот ссылка на демо-версию.

See the Pen

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

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

Комментарии


Дополнительные материалы

Предыдущая Статья

Новое в Symfony 7.2: Keepalive сообщения транспорта

Следующая Статья

Новое в Symfony 7.2: Улучшения в Mime