Функции в CSS?!

alexei14/04/2025 - 09:01
Функции в CSS?!

Очень нужный дисклеймер: Теперь в CSS мы (вроде как) можем использовать функции! Я знаю, это не самое приятное чувство - закончить читать о новой фиче только для того, чтобы автор сказал: "И, надеюсь, мы увидим её через пару лет". К счастью, прямо сейчас вы можете попробовать (неполную) версию функций CSS в Chrome Canary с экспериментальным флагом, хотя кто знает, когда мы сможем использовать на рабочем сайте.

Аргументы, значения по умолчанию и возвращаемые значения!

Я пил кофе, когда прочитал новость о том, что Chrome создаёт прототипы функций в CSS, и… я не выронил чашку из рук. Я был взволнован, но думал, что "функции" в CSS будут похожи на миксы в Sass - ну, знаете, шаблоны для создания повторно используемых шаблонов. Это, конечно, круто, но на самом деле это просто синтаксический сахар для написания меньшего количества кода CSS.

Но я внимательнее присмотрелся к примеру, и тут уж чашка чуть не выпала у меня из рук.

Аргументы?! Возвращаемые значения?! Ради этого стоит на время отложить свои дела! Мне нужно было узнать обо всем этом больше, и, к счастью, спецификация написана очень понятно. Что ещё более удивительно, вы можете использовать функции уже прямо сейчас в браузере Chrome Canary! Итак, после изучения спецификации и многочисленных экспериментов вот мои основные выводы о функциях CSS.

Что же такое функции в CSS?

Мне нравится это определение из спецификации:

Пользовательские функции предоставляют авторам те же возможности, что и пользовательские свойства, но с параметрами

Они используются в тех же местах, где вы бы использовали пользовательские свойства, но функции возвращают разные значения в зависимости от переданного аргумента. Синтаксис самой простой функции - это правило @function, за которым следует имя функции в виде <штриховой-идентификатор> + ():


@function --dashed-border() {
 /* ... */
}

Функции без аргументов похожи на пользовательское свойство, так что… Чтобы сделать их функциональными, мы можем передавать аргументы внутри скобок, и также в виде <штрихового-идентификатора>:


@function --dashed-border(--color) {
 /* ... */
}

Мы можем использовать дескриптор result, чтобы вернуть некий результат, основанный на переданном аргументе:


@function --dashed-border(--color) {
   result: 2px dashed var(--color);
}

div {
   border: --dashed-border(blue); /* 2px dashed blue */
}

Мы можем даже использовать значения по умолчанию! Просто напишите после аргумента двоеточие (:), а затем его значение по умолчанию.

@function --dashed-border(--color: red) {
   result: 2px dashed var(--color);
}

div {
  border: --dashed-border(); /* 2px dashed red */
}

У функций может быть проверка типов

Функции могут проверять тип аргументов и возвращаемых значений, что будет полезно, когда мы захотим интерполировать значение, как мы делаем с переменными, созданными с помощью @property, а также когда у нас есть встроенные условные конструкции для выполнения различных вычислений в зависимости от типа аргумента.

Чтобы добавить типы аргументов, мы передаём синтаксический компонент. Это тип, заключённый в угловые скобки, например, цвет - <color>, длина - <length>. Существуют также синтаксические множители, такие как плюс (+), для приёма списка этого типа, разделённого пробелами.


@function --custom-spacing(--a ) { /* ... */ } /* e.g. 10px */
@function --custom-background(--b ) { /* ... */ } /* e.g. hsl(50%, 30% 50%) */
@function --custom-margin(--c +) { /* ... */ } /* e.g. 10px 2rem 20px */

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


@function --progression(--current, --total) returns  {
  result: calc(var(--current) / var(--total) * 100%);
}

Единственное исключение для типов: если мы хотим принять более одного типа с помощью синтаксического комбинатора (|), нам нужно заключить типы в функцию-оболочку type():


@function --wideness(--d type( | )) { /* ... */ }

У функций могут быть аргументы-списки

Хотя в Canary это, похоже, в настоящее время не работает, в будущем мы сможем передавать в функции CSS в качестве аргументов списки, заключая их в фигурные скобки. Так, в следующем примере из спецификации передается список значений - {1px, 7px, 2px} - и возвращается сумма дополнительного аргумента с максимальным значением из этого списка.


@function --max-plus-x(--list, --x) {
  result: calc(max(var(--list)) + var(--x));
}

div {
  width: --max-plus-x({ 1px, 7px, 2px }, 3px); /* 10px */
}

Интересно, можно ли будет тогда выбирать конкретный элемент из списка? А также определять, какой длины должен быть список? Допустим, мы хотим принимать только списки, содержащие четыре элемента, затем выбирать каждый по отдельности для выполнения некоторого вычисления и возвращать его. Здесь много вопросов!

Досрочный возврат значения невозможен

Все верно, досрочный возврат значения невозможен. Это не то, что определено в спецификации, но не было опробовано, а то, что просто не будет разрешено. Таким образом, если у нас есть два варианта возвращаемого значения, один из которых заключён в правило @media или @supports, а другой находится вне этого правила в конце функции, то всегда будет возвращаться последний:


@function --suitable-font-size() {
  @media (width > 1000px) {
    result: 20px;
  }
  result: 16px; /* Всегда возвращается 16px */
}

Мы должны изменить порядок возвращаемых значений, поставив условный result последним. Это не имеет большого смысла в других языках программирования, где функция завершается после возврата чего-либо, но здесь есть своя причина, так как C в CSS расшифровывается как Cascade (Каскад): этот порядок позволяет условному результату переопределять последний результат, что очень по CSS-шному:


@function --suitable-font-size() {
  result: 16px;

  @media (width > 1000px) {
    result: 20px;
  }
}

Чего еще не хватает?

Но во всем этом есть и своя ложка дегтя.

Согласно заметке Chrome о функциях CSS, мы находимся еще на очень ранней стадии, поскольку не можем:

  • ...использовать локальные переменные. Хотя я их использовал, и они похоже работали.
  • ...использовать рекурсивные вызовы функций (они все ломают!),
  • ...перечислять аргументы,
  • ...обновлять функции и проводить соответствующие изменения стилей,
  • ...использовать правило @function в каскадных слоях или в Объектной модели CSS (CSSOM),
  • ...использовать "функции скобок Айверсона … поэтому любые запросы @media или подобные им нужно делать при помощи вспомогательных пользовательских свойств (в :root или похожих областях)."

Прочитав, что такое скобки Айверсона, я понял, что в настоящее время мы не можем использовать возвращаемое значение за правилами @media или @support. Например, следующий фрагмент кода из спецификации не должен работать:


@function --suitable-font-size() {
  result: 16px;

  @media (width > 1000px) {
    result: 20px;
  }
} 

Хотя, судя по результатам тестирования, теперь это поддерживается. Тем не менее, мы можем использовать промежуточное пользовательское свойство и вернуть его в конце:


@function --suitable-font-size() {
  --size: 16px;

  @media (width > 600px) {
    --size: 20px;
  }

  result: var(--size);
}

А как насчет миксинов? Скоро и они будут здесь. Как говорит спецификация:

На данный момент эта спецификация определяет только пользовательские функции, которые работают на уровне значений CSS. Ожидается, что позже будут определены "миксины" - функции, которые работают на уровне стилевых правил.

Ну, что ж. Будем ждать и следить за процессом.