Поддержка CSS-функций сравнения min()
, max()
и clamp()
появилась в Firefox 8 апреля 2020 года. Это означает, что данные функции теперь поддерживаются во всех основных браузерах. Эти CSS-функции расширяют наши возможности по созданию динамических макетов и по проектированию более гибких, чем раньше, компонентов. Их можно использовать для настройки размеров элементов-контейнеров, шрифтов, отступов и многого другого. Правда, веб-дизайнеру, создающему макеты страниц с учётом возможности использования этих восхитительных функций, может понадобиться научиться думать по-новому.
Сегодня я хочу рассказать об особенностях этих функций, объяснить всё то, что может оказаться в них непонятным, и привести практические примеры их использования.
Браузерная поддержка
Сначала мне хотелось бы коснуться браузерной поддержки функций min()
, max()
и clamp()
. Всё же, появились они сравнительно недавно. Надо отметить, что функции min()
и max()
пользуются одинаковым уровнем поддержки браузеров.
Поддержка функции min() (так же выглядят и сведения о поддержке max())
Ниже представлены сведения о поддержке функции clamp()
.
Поддержка функции clamp()
CSS-функции сравнения
Спецификация CSS говорит нам о том, что функции сравнения min()
, max()
и clamp()
позволяют сравнивать значения выражений и представлять одно из них. Значение, возвращаемое той или иной функцией, зависит от её особенностей. Исследуем эти функции.
Функция min()
Функция min()
принимает одно или большее количество значений, разделённых запятой, и возвращает наименьшее из них. Эту функцию используют для ограничения значений CSS-свойств жёстко заданным максимальным уровнем.
Рассмотрим следующий пример. Нам нужно, чтобы максимальная ширина элемента не превышала бы 500px
.
.element {
width: min(50%, 500px);
}
При ширине области просмотра 1150px, что больше 1000px, элемент будет иметь ширину 500px
Браузеру нужно будет выбрать наименьшее значение из 50%
и 500px
. На результат выбора влияет ширина области просмотра. Если 50% ширины области просмотра составит более чем 500px
, тогда это значение будет проигнорировано и функция возвратит 500px
.
В противном случае, если 50% ширины области просмотра составляют менее 500px
, для настройки ширины элемента будет использовано полученное значение. Как вы думаете, при какой ширине области просмотра 50% её ширины не превысят 500px
? Это, очевидно, 1000px
. При такой ширине оба значения, переданные функции, будут одинаковыми. Если ширина области просмотра будет менее 1000px
, то для настройки ширины элемента будет использована именно она.
Если ширина области просмотра менее 1000px, то для настройки ширины элемента будет использовано 50% от этой ширины
Я надеюсь, что мне удалось понятно всё это объяснить. Я долго размышлял о функции min()
, стремясь проникнуть в её сущность. Поэтому мне очень хотелось бы доходчиво о ней рассказать. Вот видео, демонстрирующее результаты применения этой функции.
Ширина элемента ограничена значением в 500px
Вот интерактивный пример, использованный при записи вышеупомянутого видео. Для того чтобы увидеть функцию min()
в действии — попробуйте изменить размеры окна браузера.
Функция max()
Функция max()
принимает одно или несколько значений, разделённых запятыми, и возвращает наибольшее из них. Эту функцию используют для того, чтобы зафиксировать минимальное значение, которое может принимать CSS-свойство.
В следующем примере нам нужно, чтобы ширина элемента составляла бы как минимум 500px
.
.element {
width: max(50%, 500px);
}
Если 50% ширины области просмотра больше 500px — для настройки ширины элемента будет использовано именно это значение
Браузеру нужно выбрать максимальное значение из 50%
и 500px
. Выбор зависит от ширины области просмотра. Если 50% ширины области просмотра — это меньше, чем 500px
, браузер это значение проигнорирует и использует значение 500px
.
Если же 50% ширины области просмотра больше 500px
, тогда ширина элемента будет установлена равной этому значению. Как видите, функция max()
является противоположностью функции min()
.
Для того чтобы лучше в этом разобраться — взгляните на эту интерактивную демонстрацию.
Функция clamp()
Функция clamp()
позволяет ограничивать диапазон изменения некоего значения, задавая его нижний и верхний пределы. Она принимает три параметра: минимальное значение, вычисляемое (рекомендованное) значение, максимальное значение. Если вычисляемое значение не выходит за пределы, ограничиваемые минимальным и максимальным значениями, переданными функции, то она возвратит именно это значение.
Вот пример.
.element {
width: clamp(200px, 50%, 1000px);
}
Здесь мы настраиваем ширину элемента, которая не должна быть меньше 200px
и больше 1000px
. Если значение в 50%
не выходит за эти пределы — используется именно это значение. Вот как это выглядит.
Если 50% ширины области просмотра попадают в диапазон 200px-1000px — для задания ширины элемента будет использоваться именно это значение. В данном случае это 575px
Разберём этот пример:
- Ширина элемента никогда не будет меньше
200px
. - Второй параметр функции, заданный как
50%
, будет использоваться только в том случае, если ширина области просмотра будет больше400px
и меньше2000px
. - Ширина элемента не превысит
1000px
.
В итоге можно сказать, что функция clamp()
позволяет задавать диапазон, в котором может изменяться передаваемое ей вычисляемое значение.
Здесь можно поэкспериментировать со страницей, при стилизации которой используется эта функция.
▍Как вычисляются результаты функции clamp()?
На MDN можно найти сведения о том, что, когда в качестве значения CSS-свойства используется функция clamp()
, она является эквивалентом конструкции, в которой применяются функции max()
и min()
. Взгляните на следующий пример:
.element {
width: clamp(200px, 50%, 1000px);
/* Это - эквивалент следующего выражения */
width: max(200px, min(50%, 1000px));
}
Значение 50%
зависит от ширины области просмотра браузера. Представим, что ширина области просмотра составляет 1150px
.
.element {
width: max(200px, min(50%, 1000px));
/* Представим, что ширина области просмотра составляет 1150px */
width: max(200px, min(575px, 1000px));
/* Промежуточный результат */
width: max(200px, 575px);
/* Итоговый результат */
width: 575px;
}
▍Контекст имеет значение
Вычисленное значение зависит от контекста. При указании вычисляемого значения можно использовать различные единицы измерения: %
, em
, rem
, vw/vh
. Даже если значение выражено в процентах, при его расчёте может использоваться либо ширина области просмотра — в том случае, если элемент находится в теге <body>
, либо ширина другого элемента, являющегося контейнером элемента.
▍Математические выражения
Стоит сказать о том, что при использовании функции clamp()
можно передавать ей математические выражения, что избавляет нас от необходимости использования функции calc()
. Спецификация говорит нам о том, что в каждом из аргументов clamp()
можно использовать полные математические выражения. Поэтому здесь нет нужды во вложенных функциях calc()
. Кроме того, если к итоговому значению нужно применить несколько ограничений, функции можно передавать более двух аргументов.
Взгляните на следующий пример:
.type {
/* Ограничиваем font-size значениями, находящимися между 12px и 100px */
font-size: clamp(12px, 10 * (1vw + 1vh) / 2, 100px);
}
Что CSS-функции сравнения изменят в том, как мы проектируем макеты страниц?
Часто бывает так, что дизайнер, проектируя страницу, ориентируется и на мобильные, и на настольные устройства, рассчитывая на два сценария её использования. А при работе над некоторыми проектами приходится принимать во внимание и большее количество сценариев.
Я вижу эту ситуацию так, как показано ниже.
Наверху — то, как проектируют страницы сегодня. Внизу — то, чего можно ожидать в будущем
Верхняя часть иллюстрации представляет собой то, как страницы проектируют в наши дни. Тут можно видеть несколько шагов, представляющих разные области просмотра, на вывод в которых должна быть рассчитана страница. Эти шаги могут, например, представлять собой значение некоего свойства. На нижней части иллюстрации представлен непрерывный диапазон значений, который можно получить, ограничивая это значение неким минимумом и максимумом. Полагаю, что в будущем, например, при настройке размера шрифта, дизайнеры будут поступать примерно так, как показано ниже.
Минимальное, рекомендованное и максимальное значения свойства
Как по мне, так это просто замечательно. Меня восхищает идея работы над проектами, компонентам которых нужна определённая гибкость. Конечно, не всем компонентам нужно быть динамическими, но некоторые из них вполне могут такими быть.
Сценарии использования
▍Боковая панель и основная область страницы
Слева — боковая панель. Справа — основная область страницы
Обычно боковые панели страниц имеют фиксированную ширину, а ширина основных областей гибко подстраивается под область просмотра. Мы можем расширить возможности боковой панели и сделать её более динамичной, сделать так, чтобы она, при достаточно большой области просмотра, занимала бы больше места. Для того чтобы этого достичь, нужно ограничить размер минимальной ширины боковой панели с использованием функции max()
.
Рассмотрим следующий пример.
.wrapper {
display: flex;
}
aside {
flex-basis: max(30vw, 150px);
}
main {
flex-grow: 1;
}
Минимальной шириной элемента aside
будет 150px
. Он примет размер 30vw
в том случае, если ширина области просмотра больше 500px
(500 * 30% = 150).
→ Вот пример
▍Размер шрифта заголовка
Гибкая настройка размера шрифта заголовка
Отличным сценарием использования функции clamp()
является гибкая настройка размера шрифта заголовков. Представим, что нам нужно, чтобы минимальным размером шрифта заголовка было бы 16px
, а максимальным — 50px
. Благодаря использованию функции clamp()
мы можем сделать так, чтобы размер шрифта находился бы в пределах этих значений, не уходя ни ниже, ни выше.
.title {
font-size: clamp(16px, 5vw, 50px);
}
Функция clamp()
— это идеальное решение вышеописанной задачи. Она позволяет обеспечить задание такого размера шрифта, благодаря которому текст остаётся доступным и удобным для чтения при разных размерах области просмотра. Если тут, например, воспользоваться функцией min()
, тогда мы потеряем контроль над размером шрифта на странице, выводимой в маленьких областях просмотра.
.title {
font-size: min(3vw, 24px); /* Не рекомендуется, так как ухудшает доступность */
}
Ниже показана иллюстрация этой идеи.
Использование функции min() приводит к тому, что на маленьком экране текст окажется слишком мелким
На экране мобильного устройства размер шрифта оказывается слишком маленьким. Поэтому не рекомендуется использовать для настройки размеров шрифтов лишь функцию min()
. Конечно, размер шрифта можно перенастроить с использованием медиазапроса, но тогда совершенно теряется смысл применения CSS-функций сравнения.
Как уже было сказано, функцию min()
можно использовать внутри функции max()
. Это позволяет воспроизвести возможности функции clamp()
.
.title {
font-size: max(16px, min(10vw, 50px));
}
Здесь можно поэкспериментировать с примером.
Хочу отметить, что после дискуссии в Твиттере оказалось, что не вполне правильно использовать 10vw
в качестве вычисляемого значения для размера шрифта. А именно, речь идёт о том, что это вызовет проблемы с доступностью в том случае, если пользователь изменит масштаб страницы в браузере.
Поэтому лучше поступить так:
.title {
font-size: clamp(16px, (1rem + 5vw), 50px);
}
Здесь в качестве вычисляемого размера шрифта указано выражение (1rem + 5vw)
. Это решает проблему.
▍Декоративные заголовки
Декоративный полупрозрачный заголовок, выведенный с использованием шрифта очень большого размера
Видите большой полупрозрачный текст, расположенный под основным текстом заголовка? Это — декоративный заголовок, размеры которого должны согласовываться с размерами области просмотра. Для формирования такого заголовка можно воспользоваться функцией max()
с передачей ей значения, выраженного в единицах измерения vw
. Это позволит сделать так, чтобы размер шрифта не был бы меньше заданного значения.
.section-title:before {
content: attr(data-test);
font-size: max(13vw, 50px);
}
→ Вот рабочий пример
▍Вывод плавных градиентов в разных областях просмотра
При настройке градиентов средствами CSS может понадобиться их динамическая настройка, направленная на то, чтобы сделать градиенты, выводимые на экранах мобильных устройств, более плавными. Взгляните на следующий пример. Здесь имеется такой градиент:
.element {
background: linear-gradient(135deg, #2c3e50, #2c3e50 60%, #3498db);
}
Градиент на мобильном экране характеризуется слишком резким переходом между цветами
Обратите внимание на то, что на мобильном экране чётко видна граница разделения цветов. Это плохо. Исправить ситуацию можно с помощью старого доброго медиазапроса. (Опа, похоже, я уже считаю медиазапросы «старой доброй технологией»).
@media (max-width: 700px) {
.element {
background: linear-gradient(135deg, #2c3e50, #2c3e50 25%, #3498db)
}
}
Хотя это — нормальный способ решения проблемы, в подобной ситуации мы можем использовать CSS-функцию min()
.
.element {
background: linear-gradient(135deg, #2c3e50, #2c3e50 min(20vw, 60%), #3498db);
}
В результате получится то, что показано ниже.
Градиент выглядит плавным при его просмотре на любом экране
→ Вот демонстрация этой идеи
▍Полупрозрачный градиент
Когда нужно разместить текст поверх фотографии, то под него следует «подложить» градиент. Это позволит облегчить чтение текста. Так же, как и в предыдущем примере, параметры градиента должны зависеть от размеров области просмотра. Взгляните на следующий рисунок.
Гибкая настройка градиента на экранах разных размеров
.element {
background: linear-gradient(to top, #000 0, transparent max(20%, 20vw));
}
Благодаря этому небольшому улучшению градиент на мобильных устройствах будет выглядеть гораздо лучше, чем прежде. Если размер градиента равняется, на настольном экране, 50
% родительского элемента, то на мобильном устройстве это уже должно быть что-то в районе 30
%. Задать минимальное значение размера градиента можно с помощью функции max()
.
→ Вот пример
▍Динамические внешние отступы
Настройка внешних отступов
Для настройки динамических внешних отступов элементов можно использовать единицы измерения, связанные с характеристиками области просмотра. Однако такой подход не всегда показывает себя хорошо. Дело в том, что пользователь может просматривать страницу на повёрнутом экране мобильного устройства с очень большой вертикальной высотой. Вот как решить эту проблему с использованием медиазапросов:
h1, h2, h3, h4, h5 {
margin: 7vh 0 1.05rem;
}
@media (max-height: 2000px) {
h1, h2, h3, h4, h5 {
margin: 2.75rem 0 1.05rem;
}
}
Хотя этот код работает правильно, того же самого эффекта можно добиться с помощью всего одной строчки кода:
h1, h2, h3, h4, h5 {
margin: min(7vh, 2.75rem) 0 1.05rem;
}
Благодаря использованию функции min()
мы задаём максимальное значение отступа в 2.75rem
. Как видите, это очень просто и удобно.
→ Вот рабочий пример
▍Ширина контейнера
Динамическая настройка ширины контейнера
Предположим, нам нужен элемент-контейнер, ширина которого должна составлять 80% от ширины его родительского элемента. При этом ширина контейнера не должна превышать 780px
. Как создать подобный контейнер? Обычно в такой ситуации поступают примерно так:
.container {
max-width: 780px;
width: 80%;
}
Но использование функции min()
позволяет ограничить максимальную ширину контейнера так:
.container {
max-width: min(80%, 780px);
}
→ Вот рабочий пример
▍Вертикальный внутренний отступ
Функция clamp()
нравится мне тем, что она идеально подходит для ограничения размеров внутренних отступов разделов страниц. Взгляните на следующий пример, в котором показана настройка верхней части страницы (hero-раздела).
Настройка внутренних отступов раздела, расположенного в верхней части страницы
Гибко настроить отступы подобного раздела можно, воспользовавшись всего одной строкой CSS-кода.
.hero {
padding: clamp(2rem, 10vmax, 10rem) 1rem;
}
→ Вот пример
▍Границы и тени
Вот видео, в котором показан элемент, имеющий широкую границу.
Элемент с широкой границей
В некоторых случаях в дизайне страниц используются элементы с широкими границами и с большими радиусами скругления углов. При выводе подобных элементов на мобильных экранах их границы нужно сделать меньше. То же самое касается и радиусов скругления углов. Использование функции clamp()
позволяет динамически настраивать параметры границ, привязывая их к ширине области просмотра.
.element {
box-shadow: 0 3px 10px 0 red;
border: min(1vw, 10px) solid #468eef;
border-radius: clamp(7px, 2vw, 20px);
box-shadow: 0 3px clamp(5px, 4vw, 50px) 0 rgba(0, 0, 0, 0.2);
}
Здесь можно поэкспериментировать с рабочим примером.
▍Расстояние между ячейками Grid-макета
Grid-макет
Интересный вариант использования функции clamp()
заключается в настройке свойства grid-gap
в Grid-макетах. В частности, речь идёт о том, чтобы на мобильных экранах расстояние между ячейками сетки было бы меньше, чем на настольных.
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: clamp(1rem, 2vw, 24px);
}
Если вас интересуют вопросы использования CSS-функций сравнения при проектировании Grid-макетов, взгляните на эту хорошую статью.
→ Вот пример к этому разделу
▍Использование в CSS-функциях сравнения значений без единиц измерения
Я, в ходе экспериментов с CSS-функциями, решил узнать о том, что получится, если передать функции clamp()
в качестве минимального значения 0
. Вот как это выглядит в коде:
.element {
width: clamp(0, 10vmax, 10rem);
}
Это — неправильно оформленный CSS-код. Полагаю, дело в том, что числа без единиц измерения использовать тут не следует.
▍Резервные механизмы для браузеров, не поддерживающих min(), max() и clamp()
Как и в случае с любыми другими новыми возможностями CSS, применяя функции min()
, max()
и clamp()
, нужно позаботиться о резервных механизмах. Для создания этих механизмов можно воспользоваться одним из следующих подходов.
1. Ручная настройка резервного механизма
Здесь под ручной настройкой понимается добавление в код свойства, пользующегося широкой поддержкой браузеров. Это свойство должно быть расположено перед свойством, в котором используются CSS-функции сравнения. Вот как это может выглядеть:
.hero {
padding: 4rem 1rem;
padding: clamp(2rem, 10vmax, 10rem) 1rem;
}
Здесь браузеры, поддерживающие clamp()
, проигнорируют первое выражение. А те, которые эту функцию не поддерживают, воспользуются первым способом настройки отступа.
2. Использование CSS-директивы supports
Директиву @supports
, предназначенную для проверки поддержки браузерами различных технологий, можно использовать для выяснения того, умеет ли браузер работать с CSS-функциями сравнения. Я предпочитаю пользоваться этим решением, а не тем, ручным, которое описано в первом пункте. Дело в том, что любой браузер, поддерживающий функции сравнения, должен поддерживать и директиву @supports
.
.hero {
/* Используется по умолчанию в браузерах, не поддерживающих min() */
padding: 4rem 1rem;
}
@supports (width: min(10px, 5vw)) {
/* Улучшение для браузеров, которые поддерживают min() */
.hero {
padding: clamp(2rem, 10vmax, 10rem) 1rem;
}
}
▍О доступности контента
В то время как CSS-функции сравнения дают нам новый способ создания более гибких веб-страниц, используя их, нужно проявлять осторожность. Например, применение одной лишь функции min()
для настройки свойства font-size
недостаточно. Дело в том, что на мобильных устройствах шрифт, размер которого задан с использованием только этой функции, может оказаться слишком маленьким. При настройке особенно важного содержимого страниц нужно помнить об ограничении минимальных и максимальных значений параметров. Иначе это может ухудшить впечатления пользователей от работы с проектом.
А пользуетесь ли вы CSS-функциями min()
, max()
и clamp()
?
Автор: ru_vds