Skip to main content

CSS функції: calc(), clamp(), min(), max()

CSS математичні функції calc(), min(), max() та clamp() обчислюють значення під час layout, дозволяючи змішувати одиниці на кшталт %, px та vw в одному оголошенні без JavaScript.

Теорія

TL;DR

  • calc() виконує арифметику з різними одиницями: width: calc(100% - 250px) відраховує фіксовану ширину від повної
  • min(a, b) повертає менше значення, max(a, b) повертає більше
  • clamp(min, val, max) означає "використовуй val, але не менше min і не більше max"
  • Аналогія: термостат. min/max обирають одну межу; clamp тримає в діапазоні; calc рахує
  • Правило вибору: потрібна математика → calc(), одна межа → min/max, обидві межі → clamp()

Швидкий приклад

css
.card { /* Старий підхід: два окремих оголошення */ width: 100%; max-width: 600px; /* Сучасний: одне оголошення покриває все */ width: clamp(300px, 50vw, 600px); /* мін 300px, ідеал 50vw, макс 600px */ font-size: min(2rem, 5vw); /* масштабується з viewport, але не переросте */ padding: calc(var(--gutter) * 2 + 1rem); /* відступ з урахуванням теми */ }

clamp(300px, 50vw, 600px) повертає 50vw на більшості екранів, опускається до 300px на маленьких і обрізається на 600px на широких. Один рядок замінює два media-query breakpoint-и.

Головна різниця

calc() обчислює вираз і повертає результат. min() та max() обирають значення зі списку. clamp(min, val, max) еквівалентно max(min, min(val, max)), але читається простіше. Всі чотири функції обчислюються під час layout, не при парсингу CSS, тому браузер перераховує їх при кожному reflow.

Коли що використовувати

  • Fluid-типографікаclamp(): font-size: clamp(1rem, 2.5vw, 1.5rem) плавно масштабується без breakpoint-ів
  • Ширина контейнера з обмеженнямmin(100% - 2rem, 800px): на мобайлі повна ширина, на десктопі фіксований максимум
  • Розмір зі зсувомcalc(): height: calc(100vh - 80px) враховує фіксований header
  • Гарантований мінімумmax(): padding: max(1rem, 2vmin) не схлопується на маленьких екранах
  • Відступи з CSS-зміннимиcalc(var(--gutter) * 2 + 1rem) для підтримки теми

Таблиця порівняння

ФункціяСинтаксисПовертаєТиповий сценарій
calc()calc(вираз)Результат арифметикиwidth: calc(100% - 250px)
min()min(val1, val2, ...)Найменше значенняfont-size: min(3rem, 8vw)
max()max(val1, val2, ...)Найбільше значенняpadding: max(1rem, 2vmin)
clamp()clamp(min, val, max)val в діапазоні [min, max]width: clamp(320px, 90vw, 1200px)
КолиПотрібна математикаОдна межаОбидві межі

Як це обробляє браузер

Браузер аналізує ці функції в CSSOM під час розрахунку стилів. calc() обчислюється після того, як браузер перевів % та viewport-одиниці в логічні пікселі. min/max/clamp порівнюють вже розраховані значення на стадії used-value згідно зі специфікацією CSS Values. JavaScript тут не задіяний. Функції перераховуються при кожному reflow, тому зміна розміру вікна автоматично дає нові значення.

Типові помилки

Помилка: немає пробілів навколо + і - в calc()

css
/* Неправильно - парсер читає 100%-20px як один токен */ width: calc(100%-20px); /* Правильно */ width: calc(100% - 20px);

* і / пробілів не потребують. Але + і - без пробілів ламають правило, бо парсер не може відрізнити оператор від знаку числа на кшталт -20px. Це найпоширеніша помилка з calc(), яка трапляється хоча б раз у кожного.

Помилка: неправильний порядок аргументів у clamp()

css
/* Неправильно - min більше за preferred, результат завжди 2rem */ font-size: clamp(2rem, 1rem, 4rem); /* Правильно */ font-size: clamp(1rem, 2rem, 4rem);

Якщо мінімум перевищує бажане значення, clamp завжди повертає мінімум. Порядок має відповідати умові: min <= preferred <= max.

Помилка: відсутні одиниці в min() або max()

css
/* Неправильно - 500 без одиниць, оголошення невалідне */ width: min(100%, 500); /* Правильно */ width: min(100%, 500px);

Помилка: ділення на змінну, яка може бути нулем

css
/* Неправильно - якщо --cols = 0, результат NaN, layout ламається */ margin: calc(100% / var(--cols, 0)); /* Правильно - захист змінної */ margin: calc(100% / max(1, var(--cols, 1)));

Де зустрічається в реальних проектах

  • TailwindCSS v3: clamp() в arbitrary values, наприклад w-[clamp(20rem,50vw,40rem)]
  • Bootstrap 5.3: clamp(1rem, 1.5vw, 1.5rem) в fluid-утилітах для font-size
  • Chakra UI: clamp(300px, 60vh, 500px) для адаптивної висоти модалок
  • React Native Web: calc(100vw - ${sidebarWidth}px) через StyleSheet
  • Замінює ResizeObserver і JS-логіку там, де обмеження є суто розмірним

Питання на співбесіді

Q: Чому calc() потребує пробілів навколо + і -, але не навколо * і /?
A: CSS-парсер не може відрізнити - як оператор від - як знаку числа, наприклад -20px. Без пробілу 100%-20px виглядає як один токен з дивним суфіксом. У множення і ділення такої неоднозначності немає, тому пробіли там не обов'язкові.

Q: Яка різниця між min(90%, 800px) та clamp(0px, 90%, 800px)?
A: Результат однаковий: обидва повертають 90% до досягнення 800px. Але clamp явно вказує нижню межу, що робить намір очевиднішим, коли ця межа має значення. Якщо нижня межа не потрібна, min читається простіше.

Q: width: calc(100% - var(--sidebar)) працює на десктопі, але дає 0px на мобайлі. Чому?
A: Змінна --sidebar не визначена в мобільному контексті або не успадковується в цей елемент. Коли custom property не встановлена, calc() повертає початкове значення, і ширина схлопується. Виправлення: calc(100% - var(--sidebar, 0px)) або правильний scope змінної.

Q (senior): Як container query units (cqw) взаємодіють з цими функціями?
A: calc(100cqw - 40px) рахує відносно найближчого контейнера, а не viewport. cqw розраховується на стадії layout контейнера і потребує container-type на батьківському елементі. Це підтримується починаючи з Chrome 105 і Firefox 110. clamp(200px, calc(100cqw - 40px), 400px) всередині @container є валідним і обчислюється коректно.

Приклади

Адаптивний контейнер без media-query

css
.container { /* Повна ширина на малих екранах, обрізається до 800px на великих */ width: min(100% - 2rem, 800px); margin-inline: auto; /* Відступ масштабується між 1rem і 3rem залежно від ширини viewport */ padding: clamp(1rem, 3vw, 3rem); } /* Екран 375px: width = 343px. Екран 1200px: width = 800px. */

min(100% - 2rem, 800px) замінює класичну пару width: 100%; max-width: 800px; margin: 0 auto. Бічні відступи враховані прямо у виразі, зайвий wrapper div не потрібен.

Fluid-система типографіки

css
:root { --text-sm: clamp(0.875rem, 1vw, 1rem); --text-base: clamp(1rem, 1.5vw, 1.125rem); --text-lg: clamp(1.125rem, 2vw, 1.5rem); --text-xl: clamp(1.5rem, 3vw, 2.5rem); --text-2xl: clamp(2rem, 4vw, 3.5rem); } h1 { font-size: var(--text-2xl); } p { font-size: var(--text-base); }

Кожен розмір плавно масштабується між своїм мінімумом і максимумом. Жодного breakpoint. На екрані 320px базовий розмір дорівнює 1rem, на 1440px досягає 1.125rem. Шкала визначається один раз в :root і використовується скрізь через custom properties.

Layout з відступом від header і auto-fit сіткою

css
.hero { /* Висота viewport мінус висота header через CSS-змінну */ min-height: calc(100vh - var(--header-h, 80px)); width: min(100% - 2rem, 960px); margin-inline: auto; } .grid { display: grid; /* Колонки підлаштовуються автоматично, кожна не менше 250px */ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: clamp(1rem, 3vw, 2rem); }

calc(100vh - var(--header-h, 80px)) відраховує висоту header динамічно. Якщо змінна не задана, використовується fallback 80px. clamp(1rem, 3vw, 2rem) для gap тримає відступи пропорційними viewport без ручних breakpoint-ів.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?