.
Как и зачем типизировать CSS-переменные?
Простая анимация
Рассмотрим простую анимацию цвета фона блока. Она работает.
#el {
background-color: red;
animation: anim infinite 3s;
}
@keyframes anim {
50% { background-color: blue; }
}
Анимация с переменными
Теперь вынесем цвета в CSS-переменные. Анимация перестала работать плавно.
#el {
--color-stop: red;
background: var(--color-stop);
animation: anim infinite 3s;
}
@keyframes anim {
50% { --color-stop: blue; }
}
Почему она не работает?
Потому что изначально CSS-переменные не типизированы. Мы можем взять переменную, положить в нее сначала ключевое слово, потом пиксели, потом calc()-выражение. И это будет валидно:
div {
--name: red;
--name: 11px;
--name: calc(14deg * 3);
}
В такой ситуации никто не запрещает нам внутри анимации положить в переменную что-то кроме цвета:
#el {
--color-stop: red;
margin: 50px;
animation: anim infinite 3s;
}
@keyframes anim {
50% {
--color-stop: 10px;
margin: var(--color-stop);
}
}
Проще говоря, если мы не сообщим браузеру, какой тип данных содержит переменная, он не сможет построить плавный переход между значениями. Ведь нельзя построить переход между цветом и значением длины.
А как сделать чтобы работала?
Нужно типизировать переменную. Для этого есть два способа. К сожалению, оба они до сих пор работают только в chromium-браузерах.
Первый способ
С помощью глобального объекта CSS и его метода registerProperty.
CSS.registerProperty({
name: '--color',
syntax: '<color>',
inherits: false,
initialValue: 'red'
})
Он принимает объект с несколькими полями:
name
- имя переменной. Прямо вместе с двумя дефисами.syntax
- тип данных в переменной, записанный на Value Definition Syntax. CSS имеет специфичную грамматику, и обычная для языков программирования форма Бекуса-Наура не удобна для ее описания. Поэтому в 1998 году был создан этот синтаксис. Подробнее о причинах его появления и о нём самом можно почитать в моей статье. Если изучать и писать эту грамматику вам не хочется, можно посмотреть ее в разделе "Формальный синтаксис" у каждого свойства на MDN или сразу в спецификации в графе value. Так же стоит учитывать, что сейчас поддерживаются далеко не все типы, вот список поддерживаемых. Цвет, юниты длины, числа. Для обычной верстки этого хватает.inherits
- булево значение, которое определяет, будет ли наследоваться переменная. Если вы указали свойство как ненаследуемое, значит в каждом блоке, где вы не определили значение переменной в CSS, она будет равнаinitialValue
.initialValue
- строка, где указано начальное значение переменной. Если вы не определите ее в CSS, ее значение будет именно такое.
Второй способ
Чуть позже в CSS появилась специальная директива, которая делает ровно то же самое, что и JS-код выше.
@property --color {
syntax: '<color>';
initial-value: red;
inherits: false;
}
Отличия:
- имя переменной указывается не в отдельном дескрипторе, а в имени директивы
- начальное значение - не строка, а собственно значение.
Анимация снова работает
Теперь можно типизировать переменную и сделать работующую анимацию на CSS-переменных:
@property --color-stop {
syntax: '<color>';
initial-value: red;
inherits: false;
}
#el {
--color-stop: red;
background: var(--color-stop);
animation: anim infinite 1s;
}
@keyframes anim {
50% { --color-stop: blue; }
}
Еще раз напоминаю, что это работает не во всех мажорных браузерах.
Анимированные градиенты
Попытаемся поменять градиент на фоне блока. Используем свойство transition
чтобы это произошло
плавно:
#el {
background-image: linear-gradient(red, blue);
transition: background-image 1s;
}
#el:hover {
background-image: linear-gradient(green, blue);
}
Почему это не работает? Потому что, когда браузер пытается построить плавный переход между двумя значениями свойства, он рассматривается целиком. То есть, когда в прошлом разделе мы рассматривали переход между двумя цвета, эти цвета были целиком значениями свойств. Теперь, он пытается построить переход не между цветами в градиенте, а между двумя градиентами. То есть, теоретически мы можем написать вот так:
#el {
background-image: linear-gradient(white, black);
transition: background-image 1s;
}
#el:hover {
background-image: radial-gradient(circle, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%);
}
Должен ли браузер пытаться выкрутиться в такой ситуации? На самом деле нет. Потому что background-image
-
дискретно
анимированное свойство. В нем могут быть даже не только градиенты, но и картинки. И не только.
Что же делать? В прошлом разделе был описан способ создания плавных переходов и мы можем применить его здесь тоже:
@property --gradient-start {
syntax: "<color>";
initial-value: red;
inherits: false;
}
#el {
--gradient-start: red;
background: linear-gradient(var(--gradient-start), blue);
transition: --gradient-start 1s;
}
#el:hover {
--gradient-start: green;
}
Здесь цвет одной из точек градиента в переменную, переменная типизирована (в ней может быть только цвет) и
свойство transition
применено на переменную, а не на свойство.
Отличный прием, который нельзя использовать прямо сейчас из-за плохой поддержки registerProperty
. Но
отлично подходит для демок и общего понимания роли типизации данных в CSS.
Источники
- Спецификация. Типизация переменных является часть проекта Houdini.
- Статья про
@property
от Chris Coyier на CSS-Tricks.