UXPUB 🇺🇦 Дизайн-спільнота

Cover image for Полное руководство по вычислительной CSS функции calc() с примерами
Саша Федорова
Саша Федорова

Опубліковано

Полное руководство по вычислительной CSS функции calc() с примерами

В CSS для выполнения основных математических операций есть специальная функция calc(). В этой статье мы рассмотрим практически все, что нужно знать об этой очень полезной функции.

Вот пример:


.main-content {
  /* Subtract 80px from 100vh */
  height: calc(100vh - 80px);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Функция calc() для значений

Единственный вариант, где вы можете использовать функцию calc() — это значения. Посмотрите эти примеры, где мы устанавливаем значение для ряда различных свойств.

.el {
  font-size: calc(3vw + 2px);
  width:     calc(100% - 20px);
  height:    calc(100vh - 20px);
  padding:   calc(1vw + 5px);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Его также можно использовать только для части ресурса, например:

.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()предназначена для различных числовых значений

Обратите внимание, что все приведенные выше примеры основаны на числах. Мы рассмотрим ряд предостережений относительно того, как можно использовать числа (потому что иногда вам не нужны единицы измерения), но это не касается строк или чего-то подобного.

.el {
  /* Nope! */
  counter-reset: calc("My " + "counter");
}
.el::before {
  /* Nope! */
  content: calc("Candyman " * 3);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Однако существует много величин CSS, и все они могут использоваться с функцией calc():

px % em rem in mm cm ptpc ex ch vh vw vmin vmax

Безразмерные величины также допустимы. Например, line-height: calc(1.2 * 1.2); а также такие свойства углов, как transform:
rotate(calc(10deg * 5));
.

Вы также можете не выполнять какие-либо расчеты, и она по-прежнему валидна:

.el {
  /* Little weird but OK */
  width: calc(20px);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Нет медиа-запросам

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

@media (max-width: 40rem) {
  /* Narrower or exactly 40rem */
}

/* Nope! */
@media (min-width: calc(40rem + 1px)) {
  /* Wider than 40rem */
}
Войти в полноэкранный режим Выход из полноэкранного режима

Когда-нибудь вы сможете делать взаимоисключающие медиа-запросы довольно логичным способом (как показано выше).

Смешанные единицы 🎉

Это, пожалуй, самая ценная функция calc()! Почти в каждом приведенном выше примере уже есть смешанные единицы, но для наглядности, взгляните на этот пример:

/* Percentage units being mixed with pixel units */
width: calc(100% - 20px);
Войти в полноэкранный режим Выход из полноэкранного режима

Это говорит, что ширина такая же, как у элемента, минус 20 пикселей.

С плавающей шириной невозможно предварительно вычислить это значение только в пикселях. Другими словами, вы не можете предварительно обрабатывать calc() с помощью Sass, чтобы сделать полифилл. Не то чтобы вам это было нужно, поскольку нет проблем с поддержкой браузера. Но дело в том, что это должно быть сделано на стороне браузера (“runtime”), когда вы смешиваете единицы измерений.

Вот еще несколько примеров смешанных единиц:

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

animation-delay: calc(1s + 15ms);
Войти в полноэкранный режим Выход из полноэкранного режима

Вероятно, их можно было бы предварительно обработать, поскольку смешанные единицы не связаны с параметрами, определяемыми runtime.

Сравнение с математическими расчетами препроцессора

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

$padding: 1rem;

.el[data-padding="extra"] {
  padding: $padding + 2rem; // processes to 3rem;
  margin-bottom: $padding * 2; // processes to 2rem; 
}
Войти в полноэкранный режим Выход из полноэкранного режима

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

Показать расчеты

calc() можно использовать, чтобы «показать расчеты» внутри CSS. Например, вам нужно рассчитать ровно 1⁄7 ширины элемента…

.el {
  /* This is easier to understand */
  width: calc(100% / 7);

  /* Than this is */
  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()

У вас есть +, -, * и /. Но они отличаются тем, как вы должны их использовать.

Сложение (+) и вычитание (-) требуют, чтобы оба числа были длинами

.el {
  /* Valid 👍 */
  margin: calc(10px + 10px);

  /* Invalid 👎 */
  margin: calc(10px + 5);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Недопустимые значения делают недействительным декларирование.

Деление (/) требует, чтобы второе число было безразмерным

.el {
  /* Valid 👍 */
  margin: calc(30px / 3);

  /* Invalid 👎 */
  margin: calc(30px / 10px);

  /* Invalid 👎 (can't divide by 0) */
  margin: calc(30px / 0);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Умножение (*) требует, чтобы одно из чисел было безразмерным

.el {
  /* Valid 👍 */
  margin: calc(10px * 3);

  /* Valid 👍 */
  margin: calc(3 * 10px);

  /* Invalid 👎 */
  margin: calc(30px * 3px);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Пробелы имеют значение

Когда речь идет о сложении и вычитании.

.el {
  /* Valid 👍 */
  font-size: calc(3vw + 2px);

  /* Invalid 👎 */
  font-size: calc(3vw+2px);

  /* Valid 👍 */
  font-size: calc(3vw - 2px);

  /* Invalid 👎 */
  font-size: calc(3vw-2px);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Отрицательные числа допустимы (например, calc (5vw – -5px)), но это пример того, когда пробелы не только необходимы, но полезны.

Таб Аткинс говорит, что причина, по которой необходим интервал между + и - на самом деле связана с проблемами синтаксического анализа. Я не могу сказать, что полностью это понимаю, но, например, 2px-3px обрабатывается как число «2» и единица измерения «px-3px». А у + есть другие проблемы. Я думал, что пробелы связаны с синтаксисом пользовательских свойств, но нет!

Умножение и деление не требуют пробелов вокруг операторов. Но, думаю, что нужно добавить пробел ради удобочитаемости и мышечной памяти для других операторов.

Пустое пространство снаружи не имеет значения. Вы даже можете делать разрывы строк, если хотите:

.el {
  /* Valid 👍 */
  width: calc(
    100%     /   3
  );
}
Войти в полноэкранный режим Выход из полноэкранного режима

Однако будьте осторожны: между calc() и открывающей скобкой не должно быть пробелов.

.el {
  /* Invalid 👎 */
  width: calc (100% / 3);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Вложение 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 {
  /* This */
  width: calc(100% + 2rem / 2);

  /* Is very different from this */
  width: calc((100% + 2rem) / 2);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Пользовательские свойства CSS и calc() 🎉

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

html {
  --spacing: 10px;
}

.module {
  padding: calc(var(--spacing) * 2);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Уверен, что вы можете настроить свойства CSS, в которых будет множество переменных, устанавливая тонны пользовательских свойств CSS, а затем позволяя остальной части CSS использовать их по мере необходимости.

Пользовательские свойства также могут ссылаться друг на друга. Вот пример, где используется ряд математических операций (обратите внимание на отсутствие функции calc() сначала). В конечном итоге они должны находиться внутри функции calc().

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

.module[data-spacing="XL"] {
  padding: calc(var(--spacing-XL));
}
Войти в полноэкранный режим Выход из полноэкранного режима

Вам это может не понравиться, поскольку нужно помнить о calc(), где вы затем используете это свойство, но это возможно и потенциально интересно с точки зрения удобочитаемости.

Пользовательские свойства могут исходить из HTML, что иногда чертовски круто и полезно. (Посмотрите, как Splitting.js добавляет индексы к словам/символам).

<div style="--index: 1;"> ... </div>
<div style="--index: 2;"> ... </div>
<div style="--index: 3;"> ... </div>
Войти в полноэкранный режим Выход из полноэкранного режима
div {
  /* Index value comes from the HTML (with a fallback) */
  animation-delay: calc(var(--index, 1) * 0.2s);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Добавьте единицы позже

В случае, если вы находитесь в ситуации, когда проще хранить числа без единиц измерения или заранее выполнять математические операции с безразмерными числами, вы всегда можете подождать, пока не примените число, чтобы добавить unit, умножив его на 1 и единицу измерения.

html {
  --importantNumber: 2;
}

.el {
  /* Number stays 2, but it has a unit now */
  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()

Функция attr() в CSS выглядит привлекательно, как будто вы можете извлекать значения атрибутов из HTML и использовать их. Но…

<div data-color="red">...</div>
Войти в полноэкранный режим Выход из полноэкранного режима
div {
  /* Nope */
  color: attr(data-color);
}
Войти в полноэкранный режим Выход из полноэкранного режима

К сожалению, здесь нет «types», поэтому attr() предназначена только для строк в сочетании со свойством content.

div::before {
  content: attr(data-color);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Я упоминаю об этом, потому что может возникнуть соблазн попытаться вывести число таким образом, чтобы использовать его в расчетах, например:

<div class="grid" data-columns="7" data-gap="2">...</div>
Войти в полноэкранный режим Выход из полноэкранного режима
.grid {
  display: grid;

  /* Neither of these work */
  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;

  /* Yep! */
  grid-template-columns: repeat(var(--columns), 1fr);
  grid-gap: calc(var(--gap));
}
Войти в полноэкранный режим Выход из полноэкранного режима

Инструменты браузера

DevTools браузера попросит вас показать вам calc() в том виде, в котором вы его создали в таблице стилей.

Firefox DevTools

Firefox DevTools

Если вам нужно определить расчетное значение, есть вкладка «Computed» (в DevTools всех браузеров, по крайней мере, тех, о которых я знаю), которая покажет вам его.

Chrome DevTools

Chrome DevTools

Поддержка браузера

calc() Поддержка браузера

Если вам действительно нужна поддержка очень старой версии браузера (например, IE 8 или Firefox 3.6) добавьте другое свойство или значение перед тем, которое использует calc():

.el {
  width: 92%; /* Fallback */
  width: calc(100% - 2rem);
}
Войти в полноэкранный режим Выход из полноэкранного режима

Также известно несколько проблем с функцией calc(), но все они относятся к старым браузерам. Существует список из 13 известных проблем. Вот несколько из них:

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

Сценарии использования

Также известно несколько проблем с функцией calc(), но все они относятся к старым браузерам. Существует список из 13 известных проблем. Вот несколько из них:

Я использовал ее для создания полноценного служебного класса: .full-bleed { width: 100vw; margin-left: calc(50% - 50vw); } Я бы сказал, что calc() входит в тройку моих любимых функций CSS.

Я использовал ее, чтобы освободить место для липкого футера.

Я использовал ее, чтобы установить fluid type / dynamic typography… вычисленный font-size, основанный на минимуме, максимуме и скорости изменения единиц области просмотра. Не только font-size, но и line-height.

Если вы используете calc() как часть fluid type, которая включает единицы области просмотра, убедитесь, что добавили единицу, использующую rem или em, чтобы у пользователя все еще был контроль над увеличением или уменьшением шрифта с помощью увеличение или уменьшение масштаба по мере необходимости.

Мне очень нравится добавлять пользовательское свойство «content width», а затем использовать его для создания необходимого интервала, например полей: .margin { width: calc( (100vw - var(--content-width)) / 2); }

Я использовал ее для создания кросс-браузерного компонента drop-cap. Вот его часть: .drop-cap { --drop-cap-lines: 3; font-size: calc(1em * var(--drop-cap-lines) * var(--body-line-height)); }

Я использовал ее, чтобы некоторые изображения выходили за пределы контейнера на странице статьи.

Я использовал ее, чтобы правильно разместить на странице визуальные элементы, комбинируя ее с отступами и единицами vw/vh.

Я использую ее, чтобы преодолеть ограничения в background-position, но особенно ограничения в позиционировании точек stop-color в градиентах. Например, «stop 0.75em short of the bottom».


Перевод статьи css-tricks.com

Топ коментарі (0)