Меня зовут София. Я - CSS инженер. Это мой блог.

Если вам очень больно без стилей, вы можете их.

.

Как и зачем типизировать 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'
})

Он принимает объект с несколькими полями:

Второй способ

Чуть позже в 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.

Источники

  1. Спецификация. Типизация переменных является часть проекта Houdini.
  2. Статья про @property от Chris Coyier на CSS-Tricks.