Рабочая группа CSS решила добавить if() в CSS, но некоторое время ее не будет в браузерах. Какие у нас есть варианты на данный момент? Как бы здорово это ни было, до этого еще далеко: два года, если все пройдет очень гладко, а если нет, то больше. Итак, что же делать, если условные выражения нужны прямо сейчас?

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

Использовать хаки на продакшне?!

Инстинктивный ответ многих разработчиков, увидев подобные хаки, звучит так: «Хороший хак, но возможно никогда не смогу использовать его в производстве». На первый взгляд это звучит разумно (сохранить поддерживаемость кодовой базы — достойная цель!), но при более глубоком рассмотрении отражает неверный порядок приоритетов, отдающий предпочтение удобству разработчика, а не пользователя.

TAG поддерживает документ [Принципы проектирования веб-платформ[1], где каждый разрабатывает API для веб-платформы, которые предполагается прочитать и следовать им. Я твердо верю, что необходимо публиковать принципы проектирования для любого продукта[2]. Они помогают не сбиться с пути и помнить об общем представлении. В противном случае его легко упустить из виду в повседневных мелочах. Одним из основных принципов документа является Приоритет групп.

Его суть такова:

Потребности пользователей важнее потребностей авторов веб-страниц, которые важнее потребностей разработчиков пользовательских агентов, которые важнее потребностей составителей спецификаций, которые важнее теоретической чистоты.

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

В качестве примера ближе можно привести веб-приложение, использующее такой фреймворк, как, например, Vue и несколько компонентов Vue, потребности пользователей сайта стоят выше потребностей разработчиков веб-приложений, которые стоят выше потребностей разработчиков компонентов Vue, которые стоят выше потребностей разработчиков фреймворка Vue.

TAG не изобрела этот принцип; в различных формулировках он хорошо известен в кругах UX и продуктовых специалистов:

  • Переложите боль на тех, кто способен ее вынести.
  • Предпочитайте внутреннюю сложность внешней сложности.

Почему? Вот несколько причин:

  • Намного проще изменить реализацию, чем изменить ориентированный на пользователя API, поэтому, чтобы сохранить его чистоту с самого начала, стоит пойти на жертвы.
  • У большинства продуктов гораздо больше пользователей, чем разработчиков, поэтому такой подход сводит коллективную боль к минимуму.
  • С внутренней сложностью можно справиться гораздо проще, при помощи инструментов или даже хороших комментариев.
  • Управление сложностью внутри локализует и лучше сдерживает её.
  • Как только базовая платформа станет лучше, чтобы получить выгоду, нужно будет изменить только одну кодовую базу.

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

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

Когда это не рекомендуется делать?

В двух словах, когда абстракция может «протечь» (т.е. её внутренние детали станут видимыми и повлияют на использование), некрасивое решение допустимо только в том случае, если оно инкапсулировано и не представлено пользователям компонентов. Если существует высокая вероятность того, что они могут столкнуться с ним, возможно, было бы лучше просто использовать атрибуты и положить конец этому.

Пример выноски с тремя --variant.

Во многих примерах ниже я использую --variant как канонический пример пользовательского свойства, которое может захотеть предоставить компонент. Однако, если потребителям компонентов может потребоваться настроить каждый вариант, возможно, лучше использовать атрибуты, чтобы они могли, например, просто написать [variant="success"], а не разбираться в том, какой сумасшедший хак применялся, чтобы предоставить пользовательское свойство --variant. И даже с точки зрения философской чистоты варианты в любом случае находятся на грани презентационного и семантического.

Текущее состояние авангарда

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

1. Двоичная линейная интерполяция

Впервые хак был задокументирован Ромой Комаровым в 2016 году, с тех пор он творчески применялся во множестве подходов. Суть метода заключается в применении формулы линейной интерполяции, чтобы преобразовать [0,1] в [a,b]:

p×a+(1−p)×b

Однако вместо того, чтобы использовать её для сопоставления одного диапазона с другим, мы используем её для сопоставления двух точек с двумя другими точками. По сути, с двумя крайними значениями обоих диапазонов: p=0 и p=1, чтобы выбрать a и b соответственно.

Таким был оригинальный пример Ромы:

:root {
		--is-big: 0;
}

.is-big {
		--is-big: 1;
}

.block {
		padding: calc(
				25px * var(--is-big) +
				10px * (1 - var(--is-big))
		);
		border-width: calc(
				3px * var(--is-big) +
				1px * (1 - var(--is-big))
		);
}

Он даже расширяет его до нескольких условий, умножая коэффициенты интерполяции. Например, вот фрагмент кода для сопоставления 0 с 100px, 1 с 20px и 2 до 3px:

.block {
		padding: calc(
				100px * (1 - var(--foo)) * (2 - var(--foo)) * 0.5 +
				20px  * var(--foo) * (2 - var(--foo)) +
				3px   * var(--foo) * (1 - var(--foo)) * -0.5
		);
}

В наши дни это можно переписать вот так, что также проясняет действующую булеву логику:

.block {
		--if-not-0: min(max(0 - var(--foo), var(--foo) - 0), 1);
		--if-not-1: min(max(1 - var(--foo), var(--foo) - 1), 1);
		--if-not-2: min(max(2 - var(--foo), var(--foo) - 2), 1);

		--if-0: var(--if-not-1) * var(--if-not-2);
		--if-1: var(--if-not-0) * var(--if-not-2);
		--if-2: var(--if-not-0) * var(--if-not-1);

		padding: calc(
				100px * var(--if-0) +
				20px  * var(--if-1) +
				3px   * var(--if-2)
		);
}

Тогда min() и max() не были доступны, поэтому Роме приходилось делить каждый множитель на скрытую константу, чтобы сделать его равным 1, когда этот множитель не был равен 0. Как только выйдет функция abs(), это станет еще проще (внутренний max() получает абсолютное значение N - var(--foo))

Ана Тюдор также писала об этом в 2018 году в очень наглядной статье: DRY Switching with CSS Variables. Я почти уверена, что она тоже использовала там булеву алгебру (умножение = AND, сложение = OR), но не смогла найти именно этот пост.

2. Переключатели (пробельные переключатели, циклические переключатели)

Они были независимо обнаружены Аной Тюдор (ок. 2017 г.), Джейн Ори в апреле 2020 г. (которая дала им название Space Toggle), Дэвидом Хурсидом (он же Дэвид К. Фортепиано) в июне 2020 года (он назвал их prop-and-lock) и искренне ваша [Леа] в октябре 2020 года (я назвала их --var: ; hack, пожалуй, это худшее из трёх название).

Основная идея заключается в том, что var(--foo, Fallback) на самом деле является очень ограниченной формой условия: если --foo является значением initial (или IACVT), оно возвращается к fallback, а в противном случае к var(--foo). Более того, для пользовательских свойств (или их резервных вариантов) можно установить пустые значения, чтобы при использовании в качестве значения свойства они игнорировались. Это выглядит так:

:root {
	--if-success: ;
	--if-warning: ;
}
.success {
	--if-success: initial;
}

.warning {
	--if-warning: initial;
}

.callout {
	background:
		var(--if-success, var(--color-success-90))
		var(--if-warning, var(--color-warning-90));
}

Один из недостатков этой версии — она поддерживает только два состояния для каждой переменной. Обратите внимание, что для двух состояний потребовались две переменные. Еще один недостаток — невозможность указать резервный вариант, если ни одна из соответствующих переменных не установлена. В примере выше, если не установлены ни --if-success, ни --if-warning, объявление background будет пустым, и поэтому оно станет IACVT, что сделает его равнвм transparent.

Циклические переключатели

В 2023 году Рома Комаров расширил метод до того, что он назвал «Циклическая зависимость пробельных переключателей», которая устраняет оба ограничения: поддерживает любое количество состояний и допускает значение по умолчанию. Основная идея заключается в том, что переменные становятся initial не только тогда, когда они не установлены или явно установлены в initial, но также и при возникновении циклов.

Что такое цикл? Цикл — это когда переменная ссылается на себя прямо или косвенно. Самый тривиальный цикл — --foo: var(--foo);, но они могут иметь любое количество шагов, например:

--a1: var(--a2);
--a2: var(--a3);
--a3: var(--a1);

Метод Ромы зависит от этого поведения, он создает циклы для всех переменных, используемых в качестве значений, кроме одной. Выглядит это так:

.info {
	--variant: var(--variant-default);

	--variant-default: var(--variant,);
	--variant-success: var(--variant,);
	--variant-warning: var(--variant,);
	--variant-error:   var(--variant,);

	background:
		var(--variant-default, lavender)
		var(--variant-success, palegreen)
		var(--variant-warning, khaki)
		var(--variant-error,   lightpink);
}

А применяется так:

.my-warning {
	--variant: var(--variant-warning);
}

Недостаток этого метода в том, что значения переменных --variant-success, --variant-warning и т. д. специфичны для --variant. Чтобы избежать конфликтов, они должны быть перемещены в пространство имён.

Слоистые переключатели

Большой недостаткок большинства этих методов, кроме методов на основе анимации, в том, что вам необходимо указать все значения свойства в одном месте, и объявление применяется независимо от того, имеет ли ваше пользовательское свойство значение. Это затрудняет наложение составных стилей и приводит к некоторой нежелательной связности.

Метод «Слоистые переключатели» Ромы Комарова в некоторых случаях решает эту проблему, позволяя отделить различные значения при помощи преимуществ каскадных слоев. Основная идея в том, что каскадные слои содержат ключевое слово revert-layer, которое приводит к игнорированию текущего относительно объявления слоя, в котором он используется. Учитывая слои без имени, для каждого блока свойств, которые мы хотим применять условно, можно просто воспользоваться правилом @layer {}.

У этого подхода есть некоторые серьезные ограничения, которые сделали его довольно непрактичным для моих случаев. Самое главное ограничение — все, что находится в слое, имеет приоритет ниже, чем любые стили без слоев. Во многих случаях это делает подход неприемлемым. Кроме того, он не сильно упрощает циклические переключения: все значения все равно нужно задавать в одном месте. Тем не менее его стоит посмотреть, поскольку есть некоторые случаи применения, когда он может быть полезен.

3. Приостановленная анимация

Основная идея этого метода в том, что приостановленную анимацию (animation-play-state: paused) всё же можно продвигать вперед, установив отрицательное значение свойства animation-delay. Например, в случае такой анимации, как animation: 100s foo, можно получить доступ к отметке 50%, установив animation-delay: -50s. Преобразовать сырые числа в значения <time> тривиально, поэтому их можно абстрагировать до простых чисел API, ориентированного на пользователя.

Вот простой пример, иллюстрирующий, как это работает:

@keyframes color-mixin {
	0% { background: var(--color-neutral-90); border-color: var(--color-neutral-80); }
	25% { background: var(--color-success-90); border-color: var(--color-success-80); }
	50% { background: var(--color-warning-90); border-color: var(--color-warning-80); }
	75% { background: var(--color-danger-90); border-color: var(--color-danger-80); }
}

button {
	animation: foo 100s calc(var(--variant) * -100s / 4 ) infinite paused;
}

Применяется он так:

.error button {
	--variant: 2;
}

Это просто иллюстрация основной идеи: свойство --variant, которое работает с числами, не является хорошим API! Хотя числа можно сопоставить с переменными, чтобы пользователи могли устанавливать --variant: var(--success).

Кажется, этот метод впервые был описан мной в 2015 году во время доклада о… круговых диаграммах (готова поклясться, что показывала его в предыдущем докладе, но не могу его найти). Я никогда не удосуживалась написать об этом, но 4 года спустя кто-то другой сделал это.

Чтобы избежать слегка интерполированных значений из-за проблем с точностью, туда можно добавить steps():

button {
	animation: foo 100s calc(var(--variant) * -100s / 4 ) infinite paused steps(4);
}

Это особенно полезно, когда 100, разделенное на количество значений, дает десятичные дроби с периодом, например 3 шага означают, что ваши ключевые кадры имеют шаг 33,33333%.

Преимущество этого метода в том, что каждое состояние определяется обычными объявлениями, без каких-то странностей.

Есть у этого метода и очевидные минусы:

  • Значения, ограниченные числами.
  • Он берет свойство animation на себя, поэтому его нельзя использовать для реальной анимации.

4. Измельчение типа

На данный момент все методы налагают ограничения на API, предоставляемый пользовательскими свойствами: это числа, получаемые методом линейной интерполяции, и странные значения, которые для методов проельного и циклического переключений, необходимо скрывать за переменными.

В октябре 2022 года Джейн Ори первой обнаружила метод, который фактически позволяет поддерживать простые ключевые слова, а это то, что нужно для большинства подобных случаев. Она назвала этот метод «Измельчение типа только на CSS».

Его основная идея в том, что если пользовательское свойство зарегистрировано (с помощью @property или CSS.registerProperty()), присвоение ему недопустимых для его синтаксиса значений делает его IACVT (значением, недопустимым во время выполнения), и оно возвращается к начальному или унаследованному.

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

Недавно я независимо экспериментировала с подобной идеей. Все началось с применения одного из моих компонентов, где я хотела реализовать свойство --size с двумя значениями: normal и large. Стилевые запросы могли бы почти помочь мне, но также мне нужно было установить flex-flow: columns для самого элемента, когда --size было large.

Конечный результат требует N + 1 правил @property, где N — количество различных значений, которые необходимо поддерживать. Первое @property — это правило, определяющее синтаксис нашего свойства actual:

@property --size {
	syntax: "normal | large",
	initial-value: normal;
	inherits: true;
}

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

@property --size-step-1 {
	syntax: "row | large";
	initial-value: row;
	inherits: false;
}

@property --size-step-end {
	syntax: "row | column";
	initial-value: column;
	inherits: false;
}

И на хосте компонента они последовательно соединяются следующим образом:

:host {
	--size-step-1: var(--size);
	--size-step-end: var(--size-step-1);
	flex-flow: var(--size-step-end);
}

И потребители компонентов получают действительно хороший API:

.my-component {
	--size: large;
}

По ссылке вы можете увидеть его в действии.

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

Можно преобразовать ключевые слова в числа, заменяя последовательные ключевые слова на <integer> в синтаксисе по одному, каждый раз с разными начальными значениями. Вот пример --variant с этим методом:

@property --variant {
	syntax: "none | success | warning | danger";
	initial-value: none;
	inherits: true;
}


@property --variant-step-1 {
	syntax: "none | <integer> | warning | danger";
	initial-value: 1;
	inherits: false;
}

@property --variant-step-2 {
	syntax: "none | <integer> | danger";
	initial-value: 2;
	inherits: false;
}

@property --variant-step-3 {
	syntax: "none | <integer>";
	initial-value: 3;
	inherits: false;
}

@property --variant-index {
	syntax: "<integer>";
	initial-value: 0;
	inherits: false;
}

.callout {
	--variant-step-1: var(--variant);
	--variant-step-2: var(--variant-step-1);
	--variant-step-3: var(--variant-step-2);
	--variant-index: var(--variant-step-3);

	/* Теперь используйте --variant-index, чтобы установить другие значения */
}

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

5. Переменное имя анимации

В 2018 году Рома Комаров открыл еще один метод, позволяющий использовать простые ключевые слова в качестве API пользовательских свойств. Рома забыл о нем, а затем в июне 2023 года открыл заново. Он до сих пор никогда не писал об этом, поэтому эти кодпены — единственная документация, которая у нас есть.

Это разновидность предыдущего метода: вместо одного правила @keyframes и переключения между фреймами через animation-delay, определите несколько отдельных правил @keyframes, каждое из которых назовите в честь ключевого слова, с которым захочется работать:

@keyframes success {
	from, to {
		background-color: var(--color-success-90);
		border-color: var(--color-success-80);
	}
}
@keyframes warning {
	from, to {
		background-color: var(--color-warning-90);
		border-color: var(--color-warning-80);
	}
}
@keyframes danger {
	from, to {
		background-color: var(--color-danger-90);
		border-color: var(--color-danger-80);
	}
}

.callout {
	padding: 1em;
	margin: 1rem;
	border: 3px solid var(--color-neutral-80);
	background: var(--color-neutral-90);

	animation: var(--variant) 0s paused both;
}

Используется вот так:

.warning {
	--variant: warning;
}

Очевидные недостатки этого метода:

  • Непрактичность за пределами теневой DOM из-за потенциальных конфликтов имен.
  • Берёт свойство animation на себя, поэтому его нельзя использовать для реальной анимации.

Улучшения

Каждый из этих методов имеет ограничения, некоторые из которых неотъемлемы по своей природе, но другие можно ослабить. В этом разделе я расскажу о кое-каких улучшениях, которые придумали другие люди или я. Я решила вынести их в отдельный раздел, ведь они затрагивают больше одного метода.

Улучшение каскада подходов на основе анимации

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

Один из способов справиться с этим — установить пользовательские свойства в ключевых кадрах анимации, которые применяются в обычных правилах. Применим его к имени переменной анимации:

@keyframes success {
	from, to {
		--background-color: var(--color-success-90);
		--border-color: var(--color-success-80);
	}
}
@keyframes warning {
	from, to {
		--background-color: var(--color-warning-90);
		--border-color: var(--color-warning-80);
	}
}
@keyframes danger {
	from, to {
		--background-color: var(--color-danger-90);
		--border-color: var(--color-danger-80);
	}
}

.callout {
	padding: 1em;
	margin: 1rem;
	border: 3px solid var(--border-color, var(--color-neutral-80));
	background-color: var(--background-color, var(--color-neutral-90));

	animation: var(--variant) 0s paused both;
}

Обратите внимание, что вы можете комбинировать два подхода (переменная animation-name и приостановленная анимация), если у вас есть два пользовательских свойства, где каждое состояние первого соответствует N различным состояниям второго. Например, --variant, которая устанавливает цвета, и светлый/темный режим в каждом варианте, который устанавливает разные цвета.

Еще один недостаток подходов на основе анимации — они берут на себя свойство animation. Если авторы захотят применить анимацию к вашему компоненту, внезапно перестанет работать куча несвязанных вещей, что не очень удобно для пользователей.

Чтобы предотвратить это, сделать можно не так уж много, но вы можете, по крайней мере, предложить выход: вместо определения анимации прямо в animation определить её в пользовательском свойстве, например --core-animations. Затем, если авторы захотят применить свои анимации, им нужно просто обязательно включить var(--core-animations) до или после.

Дискретные цветовые шкалы

Многие из подходов выше основаны на числовых значениях, которые затем сопоставляются с действительно нужными значениями. В случае чисел или размеров это легко. Но как насчет цветов?

Я дала ссылку на сообщение Ноя Либмана выше о рекурсивной color-mix(), где он представляет довольно сложный метод выбора в непрерывной цветовой шкале на основе числа от 0 до 1.

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

Позвольте мне объяснить: color-mix() принимает только два цвета, поэтому нужно вложить их, чтобы выбрать среди более чем двух цветов. Обойти это невозможно. Однако проценты, которые мы рассчитываем, могут быть очень простыми: 100%, когда хочется выбрать первый цвет, и 0% в противном случае. Я вписала эти числа в свой инструмент сопоставления диапазонов CSS (пример) и заметила закономерность: если хочется вывести 100%, наша переменная (к примеру, --variant-index) равна N-1 и 0%, когда она равна N. Можно использовать формулу 100% * (N - var(--variant-index)).

Применить это можно на каждом этапе смешивания:

background: color-mix(in oklab,
	var(--stone-2) calc(100% * (1 - var(--color-index, 0))), /* цвет по умолчанию */
	color-mix(in oklab,
		var(--green-2) calc(100% * (2 - var(--color-index))),
		color-mix(in oklab,
			var(--yellow-2) calc(100% * (3 - var(--color-index))),
			var(--red-2)
		)
	)
);

Но что происходит, когда итоговый процент составляет меньше 0 или больше 100? Как правило, проценты за пределами [0%, 100%] делают color-mix() невалидной. Это указывает, что нам нужно позаботиться, чтобы проценты оставались в пределах этого диапазона (через clamp() или max()). Однако в математических функциях нет проверки диапазона во время парсинга, так что значения просто ограничиваются допустимым диапазоном.

Вот простой пример, с которым можно поиграть (codepen).

А вот — более реалистичный: метод измельчение типа для преобразования ключевых слов в числа, а затем применение метода выше, чтобы выбрать из 4 цветов для фона и границ (codepen).

Объединение методов

Каждый метод состоит из двух компонентов: поддерживаемых входных значений, т. е. вашего API пользовательских свойств, который вы будете предоставлять, например чисел, ключевых слов и т. д., а также из поддерживаемых выходных значений (<dimension>, ключевых слов и т. д.)

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

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

Ключевые слова → ЧислаИзмельчение типа
Числа → Ключевые словаПриостановленную анимацию можно использовать для выбора из нескольких ключевых слов на основе числа, которое мы преобразуем в отрицательное значение animation-delay
Пробельные переключатели → ЧислаЛегко: --number:calc(0 var(--toggle, + 1))
Числа → Пробельные переключателиИ снова Рома Комаров придумал очень крутой трюк: он условно применяет анимацию, которая интерполирует два пользовательских свойства от initial до пустого значения и наоборот — в основном это для имён переменных анимации, но используется для внутреннего значения. К сожалению, ошибка Firefox не позволяет методу работать корректно. Рома также попробовал вариант для переключения пробелов, но совместимость у него ещё хуже, она ограничена только Chrome. Я немного изменила его идею — использовала приостановленную анимацию, и похоже, что моя попытка работает и в Firefox

Итак, какой метод лучше?

Ниже я резюмировала плюсы и минусы каждого метода:

МетодВходные значенияВыходные значенияПлюсыМинусы
Двоичная линейная интерполяцияЧислаКоличестваЛегковесный. Не требует глобальных правилОграничен выходной диапазон
Переключателиvar(--alias) (фактические значения слишком странные, чтобы их можно было предоставлять в необработанном виде)ЛюбоеМожет использоваться как часть значенияСтранные значения, которым нужно создать алиасы
Приостановленная анимацияЧислаЛюбоеОбычные, несвязанные объявленияПринимает на себя свойство animation. Каскадные странности
Измельчение типаКлючевые словаЛюбое значение, поддерживаемое синтаксисом дескриптора.Высокая гибкость открытого API. Хорошая инкапсуляция
Необходимо вставить CSS в нетеневую DOM. Утомительный код (хотя его можно автоматизировать с помощью инструментов сборки). Нет поддержки Firefox (хотя это меняется)
Переменное имя анимацииКлючевые словаЛюбоеОбычные, несвязанные объявления
Непрактично за пределами теневой DOM из-за конфликтов имен. Принимает на себя свойство animation. Каскадные странности

Самым важным соображением является API, который мы хотим предоставить пользователям компонентов. В конце концов, весь смысл в том, чтобы предоставить более удобный API, так?

Если ваше пользовательское свойство имеет смысл как число, без ухудшения удобства применения (например, --size может иметь смысл как число, но small | medium | large все же лучше, чем 0 | 1 | 2), то Двоичная линейная интерполяция, вероятно, наиболее гибкий метод для начала, и, как мы видели в разделе Объединение подходов, числа можно преобразовать во входные данные любого другого метода.

Однако в подавляющем большинстве случаев, которые я видела, API ориентир представляет собой набор простых ключевых слов высокого уровня. Такое возможно только с помощью измельчения типа и переменных имён анимации.

Из этих двух типов наилучшую инкапсуляцию даёт измельчение типа, ведь оно полностью полагается на пользовательские свойства и не перехватывает какие-то нативные.

К сожалению, тот факт, что @property еще не поддерживается в Shadow DOM, мешает работе, но поскольку эти промежуточные свойства используются только для внутренних вычислений, мы можем просто дать им теневые имена и вставить в обычную DOM.

С другой стороны, @keyframes не только разрешены, но и при использовании в Shadow DOM имеют подходящую область действия, поэтому переменное имя анимации может быть хорошим выборов, если вы не хотите использовать одни и те же ключевые слова для нескольких пользовательских свойств одного и того же компонента. Недостатки метода не являются препятствием для вашего конкретного случая.

Заключение

Думаю, что после всего этого, если у вас были какие-то сомнения в том, что нам в CSS не нужна if(), то огромное количество и весь ужас этих хаков должны развеять их.


  1. Я всегда считала, что это наш самый важный результат, и настаивала на том, чтобы расставить его по приоритетам. Недавно я даже стала его редактором. 🙃 ↩︎
  2. Здесь я употребляю слово продукт в общем смысле любого программного продукта, технологии или API, а не только коммерческих или прибыльных продуктов. ↩︎

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Lea Verou: Inline conditionals in CSS

Предыдущая статья5 команд Linux, которые облегчают жизнь программистам
Следующая статьяРазработка отказоустойчивых микросервисов с шаблонами «Повтор» и «Выключатель»