Функция calc() позволяет вычислять значение CSS-свойства прямо в коде, причём можно смешивать РАЗНЫЕ единицы измерения в одной формуле - например, проценты и пиксели. Без calc() так не получится: "50% минус 8 пикселей" в принципе нельзя записать одним обычным числом, потому что проценты и пиксели измеряют разное.
В этом уроке
Попробуй прямо сейчас: подвигай ползунок. Одна переменная --gap меняется - а сразу три РАЗНЫХ формулы (умножить на 3, умножить на 6, использовать как есть) пересчитываются одновременно на трёх разных элементах. Никаких перезагрузок - браузер вычисляет всё заново в реальном времени.
Синтаксис calc() и смешивание единиц
Внутри calc() работают четыре обычных математических оператора: сложение, вычитание, умножение и деление. Главная сила функции - в том, что она умеет складывать и вычитать значения в РАЗНЫХ единицах измерения: width: calc(50% - 40px); означает "ширина родителя минус 40 пикселей", и браузер честно считает это на лету при каждом изменении размера окна. Для умножения и деления одно из чисел обязательно должно быть просто числом без единиц: calc(2em * 5) - можно, а calc(2em * 5px) - нет, потому что "пиксели умножить на пиксели" не имеет физического смысла для размера.
.hero { height: calc(100vh - 60px); } - высота экрана минус высота шапки, классический пример на реальных проектах.
.hero { height: 100vh - 60px; } без calc() - это просто невалидное значение, вычислений без обёртки calc() в обычном CSS не бывает.
Пробелы вокруг + и - обязательны
У операторов + и - есть жёсткое требование из спецификации: с ОБЕИХ сторон обязательно должен быть пробел. Без пробела браузер не может понять, что перед ним - оператор вычитания или знак отрицательного числа. calc(50% - 8px) - это "проценты, потом минус, потом 8 пикселей", а calc(50% -8px) без пробела перед минусом браузер разбирает как "проценты, а следом сразу отрицательная длина" - что вообще не является допустимым выражением. В результате ЛОМАЕТСЯ ВСЁ правило целиком, а не просто "минус 8px" пропадает: свойство откатывается к значению по умолчанию, будто calc() там вообще не было. Для * и / пробелы не обязательны, но для единообразия их всё равно принято ставить.
width: calc(50% - 8px); - пробелы есть с обеих сторон минуса, работает правильно.
width: calc(50% -8px); - пробела перед минусом нет, всё выражение невалидно, свойство width вернётся к обычному поведению по умолчанию.
calc() и переменные вместе
Настоящая мощь раскрывается, когда calc() объединяют с CSS-переменными из прошлого урока. Там одна переменная меняла сразу несколько элементов через var() напрямую - здесь то же самое, но с математикой: calc(var(--gap) * 3) и calc(var(--gap) * 6) могут использовать одну и ту же переменную для получения РАЗНЫХ, но связанных между собой размеров. Изменится переменная - пересчитаются сразу обе формулы, потому что и переменные, и calc() живут в браузере в реальном времени, а не только на этапе сборки проекта.
:root { --unit: 8px; } .a { margin: calc(var(--unit) * 2); } .b { padding: calc(var(--unit) * 3); } - меняешь --unit один раз, пересчитываются оба отступа с сохранением пропорции.
.a { margin: 16px; } .b { padding: 24px; } без переменной и calc() - если понадобится изменить масштаб всех отступов сразу, придётся вручную искать и менять каждое число по отдельности.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Пишут calc(50% -8px) без пробела перед минусом | Всё выражение становится невалидным, свойство откатывается к значению по умолчанию | Всегда ставить пробелы с обеих сторон + и - |
| Пытаются умножить или разделить два значения с единицами | calc(2em * 5px) невалиден - непонятно, что такое "пиксели в квадрате" для размера | Для * и / одно из значений должно быть простым числом без единиц |
| Не пробуют смешивать разные единицы там, где это и есть решение задачи | Вместо простого calc(100vh - 60px) городят сложные обходные конструкции | Использовать calc() именно там, где нужно вычесть/сложить разные единицы измерения |
| Забывают, что % внутри calc() всё ещё считается от родителя как обычный процент | Ожидают одно значение, получают другое из-за незнания, от чего именно считается % | Проверять, от какого именно родителя или направления считается % внутри calc() |
Шпаргалка
| Что нужно | Как сделать |
|---|---|
| Сложение/вычитание разных единиц | calc(100% - 40px), пробелы вокруг + и - обязательны |
| Умножение/деление | calc(var(--x) * 3) - один из множителей должен быть числом |
| Экран минус шапка | calc(100vh - 60px) |
| calc() + переменная | calc(var(--gap) * 2) - пересчитывается на лету при смене переменной |
| Частая ошибка | calc(50% -8px) без пробела - ломает всё выражение целиком |
Коротко: отвечаем на вопросы
Можно ли смешивать разные единицы измерения в calc(), например проценты и пиксели?
Да, это и есть главная причина использовать calc() - обычный CSS не умеет складывать или вычитать значения в разных единицах напрямую, а calc(50% - 40px) браузер вычисляет сам, каждый раз пересчитывая процент от актуального размера родителя.
Почему у плюса и минуса в calc() обязательно должны быть пробелы с обеих сторон?
Без пробела браузер не может отличить оператор вычитания от знака отрицательного числа: calc(50% -8px) разбирается как "проценты, а следом отдельная отрицательная длина" - невалидное выражение, из-за которого откатывается всё правило целиком, а не только вычитание.
Можно ли использовать CSS-переменную внутри calc()?
Да, и это одна из самых полезных комбинаций: calc(var(--gap) * 3) пересчитывается заново каждый раз, когда меняется значение переменной --gap, даже если это происходит уже после загрузки страницы через JavaScript.