Повне керівництво по функції calc()

В CSS є особлива функція calc(), застосовувана для виконання простих розрахунків. Ось приклад її використання:

.main-content {
  /* Відняти 80px из 100vh */
  height: calc(100vh - 80px);
}

Функція calc() і значення CSS-властивостей

Єдине місце, де можна використовувати функцію calc() – це значення CSS-властивостей. Погляньте на наступні приклади, в яких ми, використовуючи цю функцію, задаємо значення різних властивостей.

.el {
  font-size: calc(3vw + 2px);
  width:     calc(100% - 20px);
  height:    calc(100vh - 20px);
  padding:   calc(1vw + 5px);
}

Функцію calc() можна застосовувати і для установки будь-якої окремої частини властивості:

.el {
  margin: 10px calc(2vw + 5px);
  border-radius: 15px calc(15px / 3) 4px 2px;
  transition: transform calc(1s - 120ms);
}

Ця функція може навіть бути частиною іншої функції, яка відповідає за формування частини якогось властивості! Наприклад, тут calc() використовується для настройки позицій зміни кольору градієнта:

.el {
  background: #1E88E5 linear-gradient(
    to bottom,
    #1E88E5,
    #1E88E5 calc(50% - 10px),
    #3949AB calc(50% + 10px),
    #3949AB
  );
}

Функція calc() – це засіб для роботи з числовими властивостями

Зверніть увагу на те, що у всіх вищенаведених прикладах ми використовуємо функцію calc() при роботі з числовими властивостями. Ми ще поговоримо про деякі особливості роботи з числовими властивостями (вони пов’язані з тим, що іноді не потрібно використання одиниць виміру). Зараз же відзначимо, що дана функція призначена для виконання операцій з числами, а не з рядками або з чимось ще.

.el {
  /* Невірно! */
  counter-reset: calc("My " + "counter");
}
.el::before {
  /* Невіно! */
  content: calc("Candyman " * 3);
}

Існує безліч одиниць виміру, які можна застосовувати в CSS для вказівки розмірів елементів і їх частин: px, %, em, rem, in, mm, cm, pt, pc, ex, ch, vh, vw, vmin, vmax. Всі ці одиниці вимірювання можна використовувати з функцією calc().

Ця функція вміє працювати і з числами, застосовуваними без вказівки одиниць виміру:

line-height: calc(1.2 * 1.2);

Її можна використовувати і для обчислення кутів:

transform: rotate(calc(10deg * 5));

Цю функцію можна застосовувати і в тих випадках, коли в переданому їй виразі ніяких обчислень не виконується:

.el {
  width: calc(20px);
}

Функцію calc() не можна застосовувати в медіа-запитах

Хоча ця функція призначена для встфновлення значень CSS-властивостей, вона, на жаль, не працює в медіа-запитах:

/* Не працює! */
@media (min-width: calc(40rem + 1px)) {
  /* Шише, за 40rem */
}

Якщо колись подібні конструкції виявляться працездатними – це буде дуже добре, так як це дозволить створювати взаємовиключні медіа-запити, що виглядають досить логічно (наприклад – такі, які показані вище).

Використання різних одиниць виміру в одному вираженні

Допустимість використання різних одиниць виміру в одному вираженні, ймовірно, можна назвати найціннішою можливістю calc(). Подібне застосовується майже в кожному з вищенаведених прикладів. Ось, просто для того, щоб звернути на це вашу увагу, ще один приклад, в якому показано використання різних одиниць виміру:

width: calc(100% - 20px);

Цей вислів читається так: «Ширина дорівнює ширині елемента, з якої віднімається 20 пікселів».

У ситуації, коли ширина елемента може змінюватися, абсолютно неможливо заздалегідь обчислити потрібне значення, користуючись тільки показниками, вираженими в пікселях. Іншими словами, не можна провести препроцессінг calc(), використовуючи щось на зразок Sass і намагаючись отримати щось на зразок поліфілла. Так робити цього і не потрібно, з огляду на те, що calc() дуже добре підтримують браузери. Сенс тут у тому, що подібні обчислення, в яких змішують значення, виражені в різних одиницях виміру, повинні бути зроблені в браузері (під час «виконання» сторінки). А саме в можливості виконання таких обчислень і полягає основна цінність calc().

Ось ще кілька прикладів використання значень, виражених в різних одиницях виміру:

transform: rotate(calc(1turn + 45deg));

animation-delay: calc(1s + 15ms);

Ці вирази, ймовірно, можна піддати препроцессінгу, так як в них змішані значення, одиниці виміру яких не пов’язані з чим-небудь, що визначаються під час роботи сторінки в браузері.

Порівняння calc() з обчисленнями, що обробляються препроцесорів

Ми тільки що сказали про те, що найкорисніша можливість calc() не піддається препроцессінгу. Але препроцессінг дає розробнику деякі можливості, що збігаються з можливостями calc(). Наприклад, при використанні Sass теж можна обчислювати значення властивостей:

$padding: 1rem;

.el[data-padding="extra"] {
  padding: $padding + 2rem;;
  margin-bottom: $padding * 2;; 
}

Тут можуть проводитися обчислення із зазначенням одиниць виміру, тут можна складати величини, виражені в одних і тих же одиницях виміру, можна множити якісь величини на значення, одиниці вимірювання яких не вказані. Але виконувати обчислення зі значеннями, вираженими в різних одиницях виміру, тут не можна. На подібні обчислення накладаються обмеження, що нагадують обмеження calc() (наприклад, числа, на які щось множать або ділять, повинні представляти собою значення без одиниць виміру).

Розкриття сенсу використовуваних в CSS числових значень

Навіть якщо не користуватися можливостями, які досяжні лише за допомогою calc(), цю функцію можна застосувати для розкриття сенсу застосовуваних в CSS значень. Припустимо, потрібно, щоб значення якогось властивості становило б в точності 1/7 ширини елемента:

.el {
  /* Це краще зрозуміти, */
  width: calc(100% / 7);

  /* ніж це */
  width: 14.2857142857%;
}

Такий підхід може виявитися корисним в чомусь схожому на якогось самописного CSS-API:

[data-columns="7"] .col { width: calc(100% / 7); }
[data-columns="6"] .col { width: calc(100% / 6); }
[data-columns="5"] .col { width: calc(100% / 5); }
[data-columns="4"] .col { width: calc(100% / 4); }
[data-columns="3"] .col { width: calc(100% / 3); }
[data-columns="2"] .col { width: calc(100% / 2); }

Математичні оператори функції calc()

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

При додаванні + і відніманні – необхідно використовувати значення з зазначеними одиницями вимірювання

.el {
  /* Вірно */
  margin: calc(10px + 10px);

  /* Невірно */
  margin: calc(10px + 5);
}

Зверніть увагу на те, що використання некоректних значень призводить до того, що конкретне оголошення також стає некоректним.

При розподілі / потрібно, щоб у другого числа не була б вказана одиниця виміру

.el {
  /* Вірно */
  margin: calc(30px / 3);

  /* Невірно */
  margin: calc(30px / 10px);

  /* Невірно (на ноль делити неможна) */
  margin: calc(30px / 0);
}

При множенні * у одного з чисел не повинна бути вказана одиниця виміру:

.el {
  /* Вірно */
  margin: calc(10px * 3);

  /* Вірно */
  margin: calc(3 * 10px);

  /* Невірно */
  margin: calc(30px * 3px);
}

Про важливість прогалин

Прогалини, які використовуються у виразах, важливі в операціях додавання і віднімання:

.el {
  /* Вірно */
  font-size: calc(3vw + 2px);

  /* Невірно */
  font-size: calc(3vw+2px);

  /* Вірно */
  font-size: calc(3vw - 2px);

  /* Невірно */
  font-size: calc(3vw-2px);
}

Тут цілком можна використовувати і негативні числа (наприклад – в конструкції на зразок calc(5vw — -5px)), але це – приклад ситуації, в якій прогалини не тільки необхідні, але ще й корисні в плані зрозумілості виразів.

Таб Аткінс сказав мені, що причина, по якій оператори додавання і віднімання повинні виділятися прогалинами, насправді, полягає в особливостях парсинга виразів. Не можу сказати, що я в повній мірі це зрозумів, але, наприклад, парсер обробляє вираз 2px-3px як число 2 з одиницею виміру px-3px. А настільки дивна одиниця виміру точно нікому не знадобиться. При парсінгу виразів з оператором складання виникають свої проблеми. Наприклад, парсер може прийняти цей оператор за частину синтаксичної конструкції, використовуваної при описі чисел. Я подумав було, що пробіл потрібен для правильної обробки синтаксису для користувача властивостей ( –), але це не так.

При множенні і діленні прогалини навколо операторів не потрібні. Але я вважаю, що можна порекомендувати використовувати прогалини і з цими операторами – заради підвищення читабельності коду, і заради того, щоб не забувати про прогалини і при введенні виразів, які використовують додавання і віднімання.

Прогалини, що відокремлюють дужки calc() від виразу, ніякої ролі не грають. Вираз, при бажанні, можна навіть виділити, перенісши на новий рядок:

.el {
  /* Можливо */
  width: calc(
    100%     /   3
  );
}

Правда, тут варто проявляти обережність. Між ім’ям функції calc і першої відкриває дужкою прогалин бути не повинно:

.el {
  /* Невірно */
  width: calc (100% / 3);
}

Вкладені конструкції: calc(calc())

Працюючи з функцією calc(), можна використовувати вкладені конструкції, але в цьому немає реальної необхідності. Це аналогічно використанню дужок без calc:

.el {
  width: calc(
    calc(100% / 3)
    -
    calc(1rem * 2)
  );
}

Немає сенсу будувати вкладені конструкції з функцій calc(), так як те ж саме можна переписати, використовуючи лише дужки:

.el {
  width: calc(
   (100% / 3)
    -
   (1rem * 2)
  );
}

Крім того, в даному прикладі не потрібні і дужки, так як при обчисленні представлених тут виразів застосовуються правила визначення пріоритету операторів. Розподіл і множення виконуються перед складанням і відніманням. В результаті код можна переписати так:

.el {
  width: calc(100% / 3 - 1rem * 2);
}

Але в тому випадку, якщо здається, що додаткові дужки дозволяє зробити код зрозуміліше, їх цілком можна використовувати. Крім того, якщо без дужок, грунтуючись лише на пріоритеті операторів, вираз буде обчислюватися неправильно, то такий вислів потребує дужках:

.el {
  width: calc(100% + 2rem / 2);

  /* зовсім інакше */
  width: calc((100% + 2rem) / 2);
}

Призначені для користувача CSS-властивості і calc()

Ми вже дізналися про одну з чудових можливостей calc(), про обчисленнях, в яких використовуються значення з різними одиницями вимірів. Ще одна цікава можливість цієї функції полягає в тому, як її можна застосовувати до призначених для користувача CSS-властивостями. Призначеним для користувача властивостям можуть бути призначені значення, які можна використовувати в обчисленнях:

html {
  --spacing: 10px;
}

.module {
  padding: calc(var(--spacing) * 2);
}

Упевнений, нескладно уявити собі набір CSS-стилів, в якому безліч налаштувань виконується в одному місці за допомогою установки значень користувальницьких CSS-властивостей. Ці значення потім будуть використовуватися в усьому CSS-коді.

Призначені для користувача властивості, крім того, можуть посилатися один на одного (зверніть увагу на те, що calc() тут не використовується). Отримані значення можуть використовуватися для установки значень інших CSS-властивостей (а ось тут вже без calc() не обійтися).

html {
  --spacing: 10px;
  --spacing-L: var(--spacing) * 2;
  --spacing-XL: var(--spacing) * 3;
}

.module[data-spacing="XL"] {
  padding: calc(var(--spacing-XL));
}

Кому-то це може здатися не дуже зручним, так як при зверненні до призначеного для користувача властивості потрібно пам’ятати про calc(). Але я вважаю це цікавим з точки зору читабельності коду.

Призначення одиниць вимірювання призначеним для користувача властивостей після оголошення цих властивостей

Припустимо, ми знаходимося в ситуації, коли в призначене для користувача властивість має сенс записати число без одиниць виміру, або в ситуації, коли подібне число зручно буде, перед реальним використанням, як-то перетворити, не користуючись одиницями вимірами. У подібних випадках для користувача властивості можна призначити значення без вказівки одиниць виміру. Коли потрібно буде перетворити це число в нове, виражене в деяких одиницях виміру, його можна буде помножити на 1 з зазначенням потрібних одиниць виміру:

html {
  --importantNumber: 2;
}

.el {
  padding: calc(var(--importantNumber) * 1rem);
}

Робота з коліром

При описі коліру з використанням форматів, таких, як RGB і HSL, використовуються числа. З цими числами можна попрацювати в calc(). Наприклад, можна задати якісь базові HSL-значення, а потім міняти їх так, як потрібно (ось приклад):

html {
  --H: 100;
  --S: 100%;
  --L: 50%;
}

.el {
  background: hsl(
    calc(var(--H) + 20),
    calc(var(--S) - 10%),
    calc(var(--L) + 30%)
  )
}

Не можна комбінувати calc() і attr()

CSS-функція attr() може здаватися досить привабливою. І правда: береш значення атрибута з HTML, а потім його використовуєш. Але…

<div data-color="red">...</div>
div {
  /* Цього робити неможна */
  color: attr(data-color);
}

На жаль, ця функція не розуміє «типів» значень. В результаті attr() підходить лише для роботи з рядками і для установки з її допомогою CSS-властивості content. Тобто – така конструкція виявляється цілком робочої:

div::before {
  content: attr(data-color);
}

Я сказав тут про це через те, що у кого-небудь може виникнути бажання спробувати витягти з HTML-коду за допомогою attr() якесь число і використовувати його в обчисленнях:

<div class="grid" data-columns="7" data-gap="2">...</div>
.grid {
  display: grid;

  /* Ни один из этих примеров работать не будет */
  grid-template-columns: repeat(attr(data-columns), 1fr);
  grid-gap: calc(1rem * attr(data-gap));
}

Правда, добре те, що це особливого значення не має, так як призначені для користувача властивості в HTML відмінно підходять для виконання таких завдань:

<div class="grid" style="--columns: 7; --gap: 2rem;">...</div>
.grid {
display: grid;

/* Працює! */
grid-template-columns: repeat(var(--columns), 1fr);
grid-gap: calc(var(--gap));
}

Браузерна підтримка

Тут можна дізнатися про підтримку функції calc()браузерами. Якщо говорити про сучасних браузерах, то рівень підтримки calc()складає понад 97%. Якщо ж потрібно підтримувати досить старі браузери (на кшталт IE 8 або Firefox 3.6), тоді зазвичай надходять так: додають перед властивістю, для обчислення значення якого використовується calc(), таке ж властивість, значення якого задається в форматі, який зрозумілий старим браузерам:

.el {
  width: 92%;
  width: calc(100% - 2rem);
}

У функції calc() існує чимало відомих проблем, але від них страждають лише старі браузери. Ось деякі з них:

Браузер Firefox нижче 59 версії не підтримує calc() у функціях, які використовуються для завдання кольору. Наприклад: color: hsl(calc(60 * 2), 100%, 50%).
IE 9-11 НЕ отрендеріть тінь, задану властивість box-shadow, в тому випадку, якщо для визначення будь-якого із значень використовується calc().
Ні IE 9-11, ні Edge не підтримують конструкцію виду width: calc() в застосуванні до осередків таблиць.

Переклад статті “A Complete Guide to calc() in CSS