Пользовательские CSS-атрибуты как механизм передачи данных из JavaScript в CSS

Перевод статьи Using CSS Custom attributes generated by JavaScript as a handover mechanism с сайта medium.com для css-live.ru, автор — Кристиан Хайльман

Обновление: изначально я слишком упростил, что пользовательские атрибуты не поддерживают конкатенацию, но благодаря Шиме Видасу, Брайану Карделу и Грегу Уитфорту ситуация прояснилась.

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

В былые времена HTML отвечал за структуру, CSS — за внешний вид, а JavaScript — за интерактивность. Или, как я сказал в своей книге в 2006 году, если бы сайт был фильмом, то HTML был бы сценарием, CSS — операторской и режиссерской работой, а JavaScript — спецэффектами.

Сегодня CSS способен на гораздо большее. У нас есть анимации, переходы, calc() и гораздо более гибкие единицы измерения вроде em, rem, vw, vh и прочих. А также интерактивность с псевдоселекторами вроде hover, focus и состояний интерактивных элементов типа кнопок. Можно даже применить хак с чекбоксами (прим. перев.: а можно и без чекбоксов) и написать полноценную игру на чистом CSS.

Это здорово! Энтузиастам CSS теперь наверняка хватит терпения и знаний, чтобы анимация или отклик на действие пользователя выглядели и вели себя именно так, как надо. CSS-движки отвечают за хорошую производительность и не убивают интерактивность или заряд батареи устройства пользователя. Производители браузеров могут сосредоточиться на оптимизации движка, а не переваливать всю ответственность за плавную работу на разработчика.

Но всё же возможности CSS не безграничны, и в некоторых случаях без JavaScript не обойтись. Частый пример – когда надо отразить текущее состояние какого-то действия в браузерном окне, или реагировать на что-то такое, чего спецификации CSS не предусмотрели.

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

В прошлом единственным способом сделать это было хранение классов у родительских элементов и удаление классов при срабатывании определённых условий. Но с пользовательскими свойствами («CSS-переменными») взаимодействие между JavaScript и CSS становится гораздо легче.

Пользовательские свойства позволяют устанавливать «переменные» в CSS и использовать их позже. К примеру:

::root { — company-blue: #369;
}
h1 { color: var( — company-blue);
}

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

Спасибо Шиме Видасу, который показал рабочее демо в Твиттере, и Брайану Карделлу, указавшему на обсуждение в рабочей группе по CSS.

Как поясняет мой коллега Грег Уитворт:

Насчет пользовательских свойств это неверно. Проблема, о которой, видимо, ты говоришь – это ограничения CSS вообще. Хотя Шиме уже показал, что конкатенация возможна, но, пожалуй, не во всех сценариях, где действительно надо просто соединить строки (напр. из “foo” calc(5+8) получится \”foo\” calc(58), так как это невалидный calc, эти части тоже преобразуются в строки, но с экранированными кавычками). Всё в переменных разбивается на токены, так что строка это или нет – всё зависит от того, как это значение распознает токенизатор. Если в идентификаторе нет синтаксических ошибок, но при этом он не совпадает ни с чем известным, то для CSS он преобразуется в строку. Всё, что передается в JS, преобразуется в строку. Вот JSbin-пример, где это хорошо видно.

Простейший способ изменить пользовательские CSS-свойства — умножить их на какое-то значение с помощью calc().

::root { --startwidth: 200;
}
h1 { width: (var( --startwidth) * 1px);
}
h2 { width: (var( --startwidth) * 0.5px);
}

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

window.addEventListener(‘scroll’, (e) => { document.body.style.setProperty(‘ --scrolly’, window.scrollY);
});

CSS:

h1 { position: fixed; width: calc(var( — scrolly)*1px); background: #339;
}

Можете опробовать это в данном JSBin.

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

P.S. Это тоже может быть интересно: