Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «CSS функції: calc(), clamp(), min(), max()». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**CSS математичні функції** `calc()`, `min()`, `max()` та `clamp()` обчислюють значення під час layout без JavaScript. `calc()` виконує арифметику з різними одиницями: `width: calc(100% - 250px)`. `min/max` обирають крайнє значення зі списку. `clamp(min, val, max)` обмежує значення діапазоном: `font-size: clamp(1rem, 2.5vw, 1.5rem)`. **Правило:** математика → `calc()`, одна межа → `min/max`, обидві → `clamp()`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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-ів.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.