Функции CSS: calc, clamp, min, max

Урок про математику в CSS: складываем единицы через calc, делаем адаптивную типографику на clamp и ставим границы значениям через min/max.

Функции вычислений CSS (calc(), clamp(), min(), max()) считают значения прямо в браузере, позволяя смешивать несовместимые на первый взгляд единицы — проценты, пиксели, rem и vw — в одном выражении.

Зачем это на практике

Вёрстка постоянно упирается в вопрос «сколько именно». Ширина блока — это «всё доступное место минус 32 пикселя отступа». Размер шрифта должен расти на широких экранах, но не бесконтрольно. Кнопка не должна становиться уже 120 пикселей и шире 320. Раньше такие задачи решались каскадом медиа-запросов и магическими числами. Функции вычислений делают условия частью самого значения — меньше брейкпоинтов, меньше дублирования, плавная адаптация вместо ступенек.

calc(): смешиваем единицы

Главный талант calc() — складывать величины разных систем, которые иначе несовместимы. Классика — «ширина 100% минус фиксированный отступ».

.content {
  /* вся ширина родителя минус сайдбар 240px */
  width: calc(100% - 240px);
}

.full-bleed {
  /* на всю ширину окна, даже из узкого контейнера */
  width: 100vw;
  margin-left: calc(50% - 50vw);
}

Внутри calc() работают +, -, *, /. Важная тонкость: вокруг + и - обязательны пробелыcalc(100%-240px) не сработает, потому что -240px читается как отрицательное число. Умножать и делить можно только на безразмерное число: calc(100% / 3) — да, calc(100% / 20px) — нет.

Особенно мощно calc() сочетается с переменными: получается «формула токенов».

:root { --space: 8px; }
.card { padding: calc(var(--space) * 2); }   /* 16px */
.card + .card { margin-top: calc(var(--space) * 3); } /* 24px */

Так из одного базового модуля строится вся ритмика отступов: меняете --space — и пропорции сетки пересчитываются разом. Ещё calc() удобен для смещений в позиционировании (top: calc(50% - 20px), чтобы центрировать элемент известной высоты) и для сеток, где из доступной ширины нужно вычесть суммарные промежутки между колонками.

clamp(): адаптивная типографика без медиа-запросов

clamp(MIN, PREFERRED, MAX) возвращает предпочтительное значение, но зажимает его между минимумом и максимумом. Это идеальный инструмент для «текучего» (fluid) размера шрифта: он плавно растёт вместе с шириной окна и останавливается на границах.

h1 {
  /* не меньше 28px, не больше 56px,
     а между ними растёт вместе с шириной экрана */
  font-size: clamp(28px, 5vw, 56px);
}

.container {
  width: clamp(320px, 90%, 1200px);
  margin-inline: auto;
}

Здесь 5vw — это 5% ширины окна. На телефоне шириной 360px это 18px, поэтому включается нижняя граница 28px. На мониторе 1920px это 96px — срабатывает верхняя граница 56px. Между ними заголовок плавно масштабируется. Один clamp() заменяет три-четыре медиа-запроса.

Чтобы шрифт реагировал ещё и на масштаб страницы (доступность), среднее значение обычно комбинируют: clamp(1rem, 0.5rem + 2vw, 2rem) — добавка в rem гарантирует рост при увеличении базового шрифта пользователем.

min() и max(): границы значениям

min() возвращает наименьший из аргументов, max() — наибольший. Звучит наоборот, чем кажется, поэтому держите в голове правило: min() ставит верхний потолок, max()нижний пол.

.card {
  /* ширина 100%, но не больше 600px */
  width: min(100%, 600px);
}

.gutter {
  /* отступ 5% ширины, но не меньше 16px */
  padding-inline: max(16px, 5%);
}

min(100%, 600px) — это компактная замена связке width: 100%; max-width: 600px;. Аргументов может быть сколько угодно, и их можно вкладывать друг в друга и в calc().

Реальные примеры вместе

Функции комбинируются — именно так пишут современные адаптивные компоненты.

:root { --gap: clamp(12px, 2vw, 32px); }

.grid {
  display: grid;
  gap: var(--gap);
  /* колонки минимум 220px, заполняют ряд */
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
}

.hero-title {
  font-size: clamp(2rem, 1rem + 4vw, 4rem);
  line-height: 1.1;
  max-width: min(90%, 60ch);
}

Вложенный min(220px, 100%) внутри minmax — известный приём: он не даёт колонке стать шире самого контейнера на очень узких экранах, где иначе появилась бы горизонтальная прокрутка.

Как это работает под капотом

Все четыре функции — это math-функции: браузер строит дерево выражения и вычисляет его на этапе вычисления стилей, уже зная контекст (ширину родителя, размер окна, базовый шрифт). Проценты и vw резолвятся в пиксели, затем выполняется арифметика. clamp(a, b, c) по спецификации эквивалентен max(a, min(b, c)) — то есть сначала ограничивается сверху, потом снизу. Поскольку всё считается во время работы страницы, при изменении размера окна или зуме значения пересчитываются автоматически — отсюда плавность без JS.

Частые ошибки

  • Нет пробелов вокруг + и -. calc(100%-10px) невалидно. Пишите calc(100% - 10px).
  • Путать min() и max(). Верхний предел задаёт min(), нижний — max(). Если блок «не растягивается до нужного», часто перепутаны именно они.
  • MIN больше MAX в clamp. Если в clamp(MIN, P, MAX) минимум окажется больше максимума, побеждает минимум — получается неожиданно крупный текст.
  • Делить на величину с единицей. calc(100% / 16px) не сработает: делитель и множитель обязаны быть безразмерными.
  • Только vw в шрифте. font-size: 5vw без rem-добавки игнорирует пользовательский зум — это проблема доступности.

Итоги

  • calc() складывает разные единицы; вокруг + и - нужны пробелы, делитель — безразмерный.
  • clamp(MIN, PREFERRED, MAX) даёт текучую типографику и размеры без медиа-запросов.
  • min() — верхний потолок, max() — нижний пол значения.
  • min(100%, 600px) заменяет пару width + max-width.
  • Функции свободно вкладываются друг в друга и в переменные — основа адаптивных компонентов.
Проверьте себя
1. Почему правило width: calc(100%-240px); не работает?
Acalc() не поддерживает проценты
BНельзя смешивать % и px
CВокруг знака - обязательны пробелы; без них -240px читается как отрицательное число, а не вычитание
DНужно писать calc[100% - 240px] в квадратных скобках
2. Что делает font-size: clamp(28px, 5vw, 56px);?
AВсегда задаёт 5vw
BРазмер шрифта плавно растёт как 5vw, но не опускается ниже 28px и не поднимается выше 56px
CБерёт случайное значение между 28px и 56px
DСкладывает 28px + 5vw + 56px
3. Какая запись эквивалентна паре width: 100%; max-width: 600px;?
Awidth: max(100%, 600px);
Bwidth: min(100%, 600px);
Cwidth: clamp(100%, 600px);
Dwidth: calc(100% / 600px);