От автора: настраиваемые свойства CSS для каскадных переменных, давно запрошенные, но все еще недостаточно используемые, предлагают революционные возможности для совместной работы и повторного использования кода..
Переменные в CSS были крайне востребованной функцией рабочей группы CSS Консорциума World Wide Web, к которой я присоединилась в 2012 году, с самого начала группы в 1997 году. К концу 2000-х годов сообщество разработчиков — в своем стремлении уменьшить дублирование и оптимизировать работу — разработало много решений для смягчения отсутствия переменных CSS: сначала пользовательские скрипты PHP, а затем препроцессоры, такие как Less и Sass. Рабочая группа знала, что нативное решение уменьшит потребность в инструментах; нам все еще нужно было обращаться к переменным. Первый рабочий проект модуля переменных CSS был опубликован примерно в 2012 году, и пользовательские свойства CSS для каскадных переменных (так как они были более точно переименованы) наконец приобрели популярность в 2017 году благодаря поддержке браузеров.
Сегодня, однако, переменные CSS остаются плохо понятыми. После прочтения этой статьи, я надеюсь, вы лучше поймете различия между декларативными переменными CSS и переменными в других языках программирования и то, как вы можете использовать их мощь. Вперед!
Переменные CSS в двух словах
Переменные CSS — это пользовательские свойства, которые обычно каскадируются и даже наследуются. Они начинаются с зарезервированного префикса —, и нет никаких конкретных правил относительно их значений. (Допустимо все, даже пробел.) Они свободно анализируются во время объявления, но обработка ошибок не выполняется, пока они не используются в непользовательском свойстве. На их значения ссылаются через функцию var(—name), которую можно использовать в любом свойстве CSS. Функция var() также поддерживает второй аргумент, запасной вариант в случае, если переменная не установлена.
Как насчет поддержки браузерами?
Переменные CSS в настоящее время поддерживаются для 93 процентов пользователей во всем мире. Если браузер не поддерживает переменные CSS, он также не понимает функцию var() и не знает, что означает ее второй аргумент. Вместо этого нам нужно использовать каскад, как мы делаем для каждой новой функции CSS. Например:
background: red; background: var(--accent-color, orange);
В зависимости от браузера и свойства —accent-color, возможны четыре варианта. Во-первых, если браузер не поддерживает переменные CSS, он игнорирует вторую строку и применяет красный фон. Во-вторых, если браузер поддерживает переменные CSS и —accent-color установлена, этот цвет станет фоновы. В-третьих, если браузер поддерживает переменные CSS и —accent-color не установлена, будет использоваться оранжевый цвет — это запасной вариант var(). В-четвертых, если браузер поддерживает CSS-переменные и —accent-color задана, но значение не имеет смысла для свойства (например, 42deg), фон будет — внимание — прозрачным.
Последний результат может не иметь для вас смысла. Почему прозрачный? В конце концов, если бы мы написали это:
background: red; background: 42deg;
Мы бы получили красный фон, поскольку вторая строка была бы выброшена вместе со всем, что браузер не понимает. В фрагменте кода, который включает в себя 42deg без переменной, браузер выбросит ее во время анализа. Однако, с переменной, браузер не будет знать, является ли объявление действительным, до последнего момента. К тому времени он отбросит все другие значения каскада (поскольку он содержит только одно вычисленное значение) и вернется к исходному значению — в данном случае прозрачный фон.
Когда резервные значения не помогают
Резервные значения для старых браузеров подходят для простых случаев использования, но запросы функций CSS — правило @supports — могут предоставить совершенно иной CSS. Рассмотрим следующий пример, который устанавливает красный фон в браузерах, которые не поддерживают переменные CSS, и зеленый в браузерах, которые поддерживают:
html { background: red; } @supports (--css: variables) { html { background: green; } }
Запросы функций имеют также отрицательный вариант, что позволяет условно добавить CSS только в браузерах, не поддерживающих переменные CSS, написав @supports not (—css: variables). Однако, в этом случае он будет читаться только в браузерах, которые и поддерживают запросы функций, и не поддерживают переменные CSS — довольно небольшой набор.
Чем переменные CSS отличаются от переменных препроцессора?
CSS-препроцессоры — это программы, которые выполняются один раз, генерируя и отправляя статический код CSS. Переменные препроцессоров ведут себя подобно переменным императивных языков программирования, с лексической областью видимости и несколькими значениями в ходе выполнения. Их можно использовать в любом месте таблицы стилей: селекторы, условные выражения, свойства, значения и т. д., даже для создания только части значения или селектора.
Переменные CSS напротив, могут использоваться только в значениях и только для целых токенов. Они реактивны и остаются действительными для всей страницы. Они используют динамическую область видимости для каждого элемента и не могут быть частью обязательных вычислений, поскольку имеют только одно значение для каждого данного состояния. Когда переменные CSS устанавливаются извне (например, с помощью HTML или JavaScript), используйте их для чистых данных, а не для значений CSS, таких как длина или проценты.
Динамическая область вместо лексической
Область действия переменных в препроцессорах задается вложенным блоком в фигурных скобках. Однако, поскольку переменные CSS являются свойствами, их область видимости (в отличие от переменных препроцессора) основана на DOM. Это означает, что переменные CSS разрешаются для каждого элемента, а не для области действия, и они наследуются как обычные свойства. Возьмем следующий пример переменных CSS:
body { --shadow-color: gray; } button { box-shadow: .1em .1em .1em var(--shadow-color); } button:hover { --shadow-color: skyblue; }
Серая тень кнопки становится небесно-голубой, когда на кнопку наведен курсор. Давайте попробуем преобразовать это в язык препроцессора Sass:
body { $shadow-color: gray; } button { box-shadow: .1em .1em .1em $shadow-color; } button:hover { $shadow-color: skyblue; }
Результатом будет синтаксическая ошибка: «undefined variable on line 6″. Sass не знает, что button находится внутри body (потому что он не выполняется в контексте HTML, который CSS имеет в браузере), или что a button:hover также является кнопкой.
Переменные CSS являются реактивными
Самое важное отличие от переменных препроцессора заключается в том, что переменные CSS являются реактивными. Они остаются активными на протяжении всей жизни страницы, и их обновление обновляет все отношения, которые ссылаются на них. В этом смысле они больше похожи на свойства в реактивных средах, таких как Angular, Mavo и Vue, чем на переменные в традиционных языках программирования или препроцессорных языках. Поскольку они являются свойствами, их можно обновлять с помощью любого механизма, который обновляет свойства CSS: таблицы стилей, встроенные стили, даже JavaScript.
Циклы делают переменные CSS недопустимыми во время вычисления значения
Рассмотрим следующий фрагмент Sass:
.foo { $i: 1; z-index: $i; $i: $i + 1; z-index: $i; }
Вывод будет примерно таким:
.foo { z-index: 1; z-index: 2; }
Переменные CSS — это совершенно другая история. Давайте рассмотрим тот же фрагмент, но с переменными CSS:
.foo { --i: 1; z-index: var(--i); --i: calc(var(--i) + 1); z-index: var(--i); }
Переменная, зависящая от самой себя, создаст бесконечный цикл, которого у нас не может быть в декларативном языке, таком как CSS. Чтобы избежать этого, циклы отображают все задействованные переменные как недействительные во время вычисленного значения. Это включает в себя циклы длиной 1 (как в примере выше) и более длинные циклические цепочки, где переменная A зависит от B, которая зависит от C, и так далее до Z — которая зависит от A. Все 26 из них будут недействительными при вычислении значение времени, что сделает их значение, равным initial, и в основном они будут иметь один и тот же результат, как если бы они никогда не были установлены.
Переменные CSS облегчают реальное разделение поведения и стиля
Реактивность переменных CSS — это не просто философское отличие; это делает их мощным инструментом. При правильном использовании стиль может оставаться в CSS, а вычисления — в JavaScript. Чтобы сделать эту концепцию менее абстрактной, предположим, что нам нужен радиальный градиентный фон, в котором центральная точка градиента следует за курсором мыши. Раньше нам приходилось генерировать весь градиент в JavaScript и устанавливать его для встроенного стиля элемента root при каждом перемещении мыши. С помощью переменных CSS, JavaScript может установить только две переменные CSS: —mouse-x и —mouse-y. В чистом JavaScript это может выглядеть так:
var root = document.documentElement; document.addEventListener("mousemove", evt => { let x = evt.clientX / innerWidth; let y = evt.clientY / innerHeight; root.style.setProperty("--mouse-x", x); root.style.setProperty("--mouse-y", y); });
Дизайнер(ы) может затем настроить эффект на свое усмотрение, не связываясь с разработчиком(ами). Например:
html { min-height: 100vh; background: radial-gradient( at calc(100% * var(--mouse-x, .5)) calc(100% * var(--mouse-y, .5)), transparent, black 80%) gray; }
Смотрите этот эффект в действии на CodePen.
Для совершенно другого эффекта, с многослойными градиентами, которые используют одну и ту же центральную точку:
html { min-height: 100vh; --center: calc(100% * var(--mouse-x, .5)) calc(100% * var(--mouse-y, .5)); background: radial-gradient(circle at var(--center), yellowgreen, transparent 3%), conic-gradient(at var(--center), yellowgreen, green, yellowgreen); }
Смотрите этот эффект в действии на CodePen.
Предположим, мы хотим, чтобы оттенки и угол в коническом градиенте изменялись в зависимости от времени суток. Тот же JavaScript будет работать для курсора мыши, и мы могли бы добавить немного JavaScript, который устанавливает переменную CSS с текущим часом (0–23):
var updateHour = () => { var hour = new Date().getHours(); root.style.setProperty("--hour", hour); }; setInterval(updateHour, 60000); updateHour();
Затем мы можем включить эту переменную в CSS и использовать ее в вычислениях:
html { min-height: 100vh; --center: calc(100% * var(--mouse-x, .5)) calc(100% * var(--mouse-y, .5)); --hue: calc(var(--hour) * 15); --darkColor: hsl(var(--hue), 70%, 30%); --lightColor: hsl(var(--hue), 70%, 50%); background: conic-gradient(at var(--center), var(--darkColor) calc(var(--hour) / 24 * 1turn), var(--lightColor) 0); }
Смотрите этот эффект в действии на CodePen.
Поскольку все три переменные, установленные с помощью JavaScript, являются чистыми данными, а не значениями CSS, мы также можем использовать их в нескольких несвязанных правилах CSS. (Они не относятся к нашему фоновому эффекту.)
Переменные CSS облегчают инкапсуляцию стиля
Переменные CSS также позволяют повторно использовать и настраивать код CSS, поскольку они делают возможной инкапсуляцию. Предположим, мы создали стиль плоской кнопки, примененный к классу .flat. Его (упрощенный) код CSS выглядит следующим образом:
button.flat { border: .1em solid black; background: transparent; color: black; } button.flat:hover { background: black; color: white; }
Предположим, нам нужны разноцветные кнопки для разных действий, например, красная кнопка для опасного действия. Мы могли бы поддерживать класс модификатора .danger и переопределить соответствующие объявления:
button.flat.danger { border-color: red; color: red; } button.flat.danger:hover { background: red; color: white; }
Чтобы избежать дублирования, давайте заменим цвет на переменную:
button { --color-initial: black; border: .1em solid var(--color, var(--color-initial)); background: transparent; color: var(--color, var(--color-initial)); } button:hover { background: var(--color, var(--color-initial)); color: white; }
Теперь темизация заключается в переопределении всего одного свойства —color:
button.flat.danger { --color: red; }
Мы могли бы даже создавать темы на лету, переопределяя свойство встроенного стиля кнопки —color. В то время, как это является торжеством лаконичности, это гораздо больший триумф инкапсуляция — это роскошь, которую разработчики CSS не всегда могут себе позволить. Фактически, использование стороннего CSS-кода означало глубокое знакомство с его внутренней структурой, а изменение стороннего кода требовало столь же значительных изменений в коде темы. Теперь пользовательские свойства CSS могут выступать в роли своего рода API: кардинально изменять базовый стиль с помощью только пользовательских свойств.
Возвращаясь к нашему примеру с кнопками, предположим, что мы хотим добавить переход для более плавного эффекта наведения. Мы также хотим, чтобы новый цвет фона расширялся внутрь от границы, а не по умолчанию, как мы получаем при переходе между двумя цветами фона. Для этого нам нужно имитировать фон с помощью вставки box-shadow:
button.flat { --color-initial: black; border: .1em solid var(--color, var(--color-initial)); background: transparent; color: var(--color, var(--color-initial)); transition: 1s; } button.flat:hover { box-shadow: 0 0 0 1em var(--color, var(--color-initial)) inset; color: white; }
Несмотря на то, что наша реализация довольно существенно изменилась, все разные цветовые темы кнопки работают. Если бы нам нужно было переопределить CSS для создания тем, то наш код темы сломался бы.
Эта функция — если не экономить несколько символов или время на обслуживание — является истинной ценностью пользовательских свойств. Они позволяют нам обмениваться кодом, который может быть повторно использован и настроен другими разработчиками, но при этом позволяют полностью изменить его внутреннюю структуру. Пользовательские свойства также являются единственным способ задания темы компонентов теневого DOM, потому что, в отличие от любых других CSS свойств, они выходят за границы теневого DOM.
Создание цветовых палитр
Как правило, руководства по стилю начинаются с одного или нескольких базовых цветов, от которых создается остальная часть палитры. Использование переменных только для базовых цветов более эффективно, чем ручная настройка каждого варианта цвета. К сожалению, CSS еще не имеет функции изменения цвета. (Она готовится в CSS Color 5, редактируемой вами!) Тем временем, вместо того, чтобы определять базовый цвет как одну переменную, нам нужно использовать отдельные переменные для компонентов цвета.
Из цветовых синтаксисов, доступных в настоящее время в CSS, hsl() лучше всего подходит для создания вариаций цветов (пока мы не получим lch(), что намного лучше из-за его более широкого диапазона и однородности восприятия). Если мы ожидаем, что нам нужны только более светлые / темные и альфа-варианты, мы можем использовать переменную как для оттенка, так и для насыщенности:
:root { --base-color-hs: 335, 100%; --base-color: hsl(var(--base-color-hs), 50%); --base-color-light: hsl(var(--base-color-hs), 85%); --base-color-dark: hsl(var(--base-color-hs), 20%); --base-color-translucent: hsla(var(--base-color-hs), 50%, .5); }
Мы можем использовать эти переменные в CSS или создавать новые варианты на лету. Да, еще есть небольшое дублирование — яркость основного цвета — но если мы планируем создать много альфа-вариантов, мы могли бы создать переменную со всеми тремя координатами или одну с яркостью.
Предотвращение наследования
Когда переменные CSS используются для хранения данных, их поведение по умолчанию — наследование — желательно: мы определяем переменные в корневом элементе и можем переопределить их в поддереве. Но в некоторых случаях наследование не является желательным. Рассмотрим следующий пример, в котором используется переменная для применения тонкого свечения с предопределенным цветом, смещением и радиусом размытия, но с переменным распространением:
* { box-shadow: 0 0 .3em var(--subtle-glow) gold; } p { font: 200%/1 sans-serif; --subtle-glow: .05em; }
Смотрите этот (исправленный) эффект в действии на Dabblet.
Это приводит к тому, что тонкое свечение применяется не только к элементу <p>, но и ко всем его дочерним элементам, включая ссылки <a>, выделение <em> и т. д. Упс.
Чтобы это исправить, нам нужно отключить наследование, добавив к первому правилу —subtle-glow: initial. Поскольку прямое определение всегда имеет приоритет над унаследованными правилами, оно будет переопределять унаследованные значения, но из-за низкой специфичности * оно уступит всему, что указано в элементе.
Волшебное будущее пользовательских свойств CSS
Пользовательские свойства являются мощным и хорошо поддерживаемым дополнением к CSS, и их истинный потенциал еще предстоит полностью изучить. Houdini, гибрид рабочей группы W3C Technical Architecture Group и CSS Working Group, работает над API, которые расширили бы «Магические» аспекты движка рендеринга (то есть те, которые ранее были недоступны через код), многие из которых будут доступны в ближайшем будущем. Представьте, к примеру, классные эффекты, которые мы будем создать, когда сможем анимировать переменные CSS! Шоу только начинается.
Автор: Lea Verou
Источник: https://increment.com
Редакция: Команда webformyself.