Руководство по стратегии для пользовательских свойств CSS

Опубликовано: 2022-03-10
Краткое резюме ↬ Динамические свойства открывают возможности для новых творческих идей, а также могут усложнить CSS. Чтобы получить от них максимальную отдачу, нам может понадобиться стратегия написания и структурирования CSS с пользовательскими свойствами.

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

Пользовательские свойства имеют огромный потенциал для изменения того, как мы пишем и структурируем CSS и, в меньшей степени, как мы используем JavaScript для взаимодействия с компонентами пользовательского интерфейса. Я не собираюсь заострять внимание на синтаксисе и том, как они работают (для этого я рекомендую вам прочитать «Пришло время начать использовать пользовательские свойства»). Вместо этого я хочу более подробно рассмотреть стратегии получения максимальной отдачи от пользовательских свойств CSS.

Чем они похожи на переменные в препроцессорах?

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

В SCSS мы используем символ доллара для обозначения переменной:

 $smashing-red: #d33a2c;

В Less мы используем символ @ :

 @smashing-red: #d33a2c;

Пользовательские свойства следуют аналогичным соглашениям и используют префикс -- :

 :root { --smashing-red: #d33a2c; } .smashing-text { color: var(--smashing-red); }

Одно важное различие между пользовательскими свойствами и переменными в препроцессорах заключается в том, что пользовательские свойства имеют другой синтаксис для присвоения значения и получения этого значения. При получении значения пользовательского свойства мы используем функцию var() .

Еще после прыжка! Продолжить чтение ниже ↓

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

 $breakpoint: 800px; $smashing-red: #d33a2c; $smashing-things: ".smashing-text, .cats"; @media screen and (min-width: $breakpoint) { #{$smashing-things} { color: $smashing-red; } }

Большинство приведенных выше примеров были бы недействительны при использовании пользовательских свойств.

На пользовательские свойства распространяются те же правила относительно того, где они могут использоваться, как и на обычные свойства CSS. Гораздо лучше думать о них как о динамических свойствах, чем как о переменных. Это означает, что их можно использовать только внутри блока объявлений, или, другими словами, пользовательские свойства привязаны к селектору. Это может быть селектор :root или любой другой допустимый селектор.

 :root { --smashing-red: #d33a2c; } @media screen and (min-width: 800px) { .smashing-text, .cats { --margin-left: 1em; } }

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

 .smashing-text, .cats { color: var(--smashing-red); margin: 0 var(--margin-horizontal); padding: calc(var(--margin-horizontal) / 2) }

Однако их нельзя использовать в медиа-запросах или селекторах, включая :nth-child() .

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

Динамический против статического

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

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

 $background: blue; .blue { background: $background; } $background: red; .red { background: $background; }

приводит к:

 .blue { background: blue; } .red { background: red; }

Как только это отображается в CSS, переменные исчезают. Это означает, что потенциально мы можем прочитать файл .scss и определить его вывод, ничего не зная о HTML, браузере или других входных данных. Это не относится к пользовательским свойствам.

У препроцессоров есть своего рода «блочная область», в которой переменные могут быть временно изменены внутри селектора, функции или миксина. Это изменяет значение переменной внутри блока, но оно остается статическим. Это привязано к блоку, а не к селектору. В приведенном ниже примере переменная $background изменяется внутри блока .example . Он возвращается к начальному значению вне блока, даже если мы используем тот же селектор.

 $background: red; .example { $background: blue; background: $background; } .example { background: $background; }

Это приведет к:

 .example { background: blue; } .example { background: red; }

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

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

 a { --link-color: black; } a:hover, a:focus { --link-color: tomato; } @media screen and (min-width: 600px) { a { --link-color: blue; } } a { color: var(--link-color); }

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

Глобальный против локального

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

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

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

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

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

Глобальные переменные обычно статичны

Есть несколько небольших исключений, но, вообще говоря, большинство глобальных вещей в CSS тоже статичны.

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

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

Локальные статические переменные в порядке (иногда)

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

Локальные статические переменные прекрасно подходят во многих ситуациях. Я использую переменные препроцессора в файлах компонентов в основном для удобства разработчиков.

Рассмотрим классический пример компонента кнопки с несколькими вариантами размеров.

кнопки

Мой scss может выглядеть примерно так:

 $button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { // Visual styles } .btn-sml { font-size: $button-sml; } .btn-med { font-size: $button-med; } .btn-lrg { font-size: $button-lrg; }

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

Поскольку большинство статических переменных являются глобальными, мне нравится различать статические переменные, которые используются только внутри компонента. Для этого вы можете добавить к этим переменным префикс имени компонента или использовать другой префикс, например c-variable-name для компонента или l-variable-name для локального. Вы можете использовать любой префикс или префикс глобальных переменных. Что бы вы ни выбрали, полезно различать, особенно при преобразовании существующей кодовой базы для использования пользовательских свойств.

Когда использовать пользовательские свойства

Если можно использовать статические переменные внутри компонентов, когда нам следует использовать пользовательские свойства? Преобразование существующих переменных препроцессора в пользовательские свойства обычно не имеет особого смысла. Ведь причина кастомных свойств совсем в другом. Пользовательские свойства имеют смысл, когда у нас есть свойства CSS, которые изменяются относительно условия в DOM — особенно динамическое условие, такое как :focus , :hover , медиа-запросы или с помощью JavaScript.

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

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

Примечание . Знаете ли вы, что $var является допустимым значением для пользовательского свойства? Последние версии Sass распознают это, и поэтому нам нужно интерполировать переменные, присвоенные пользовательским свойствам, например: #{$var} . Это говорит Sass, что вы хотите вывести значение переменной, а не просто $var в таблице стилей. Это необходимо только для таких ситуаций, как настраиваемые свойства, где имена переменных также могут быть допустимыми CSS.

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

 $button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { --button-size: #{$button-sml}; } @media screen and (min-width: 600px) { .btn-med { --button-size: #{$button-med}; } .btn-lrg { --button-size: #{$button-lrg}; } } .btn { font-size: var(--button-size); }

Здесь я создаю одно пользовательское свойство: --button-size . Это настраиваемое свойство изначально относится ко всем элементам кнопки с использованием класса btn . Затем я изменяю значение --button-size выше 600 пикселей для классов btn-med и btn-lrg . Наконец, я применяю это пользовательское свойство ко всем элементам кнопки в одном месте.

Не будь слишком умным

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

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

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

Недавно я прочитал отличную статью на эту тему на Free Code Camp Medium. Он был написан Биллом Суруром и называется «Не делайте этого во время выполнения». Делайте это во время разработки». Вместо того, чтобы перефразировать его аргументы, я дам вам прочитать их.

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

Один пример, который недавно проиллюстрировал это для меня, был следующим:

 :root { --font-scale: 1.2; --font-size-1: calc(var(--font-scale) * var(--font-size-2)); --font-size-2: calc(var(--font-scale) * var(--font-size-3)); --font-size-3: calc(var(--font-scale) * var(--font-size-4)); --font-size-4: 1rem; }

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

В этом примере каждое пользовательское свойство определяется с помощью calc() , беря значение предыдущего пользовательского свойства и умножая его на коэффициент. Делая это, мы можем получить следующее число в шкале.

Это означает, что коэффициенты рассчитываются во время выполнения, и вы можете изменить их, обновив только значение --font-scale . Например:

 @media screen and (min-width: 800px) { :root { --font-scale: 1.33; } }

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

Хотя приведенный выше пример полезен для прототипирования, в продакшне я бы предпочел видеть что-то вроде этого:

 :root { --font-size-1: 1.728rem; --font-size-2: 1.44rem; --font-size-3: 1.2em; --font-size-4: 1em; } @media screen and (min-width: 800px) { :root { --font-size-1: 2.369rem; --font-size-2: 1.777rem; --font-size-3: 1.333rem; --font-size-4: 1rem; } }

Как и в примере из статьи Билла, я считаю полезным увидеть, каковы фактические значения. Мы читаем код намного чаще, чем пишем его, а глобальные значения, такие как масштаб шрифта, редко меняются в процессе производства.

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

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

Изменить значение, а не переменную

Изменение значения, а не переменной — одна из наиболее важных стратегий эффективного использования пользовательских свойств.

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

В этом примере у нас есть два пользовательских свойства, которые используются в примере компонента. Я переключаюсь со значения --font-size-small на --font-size-large в зависимости от размера экрана.

 :root { --font-size-small: 1.2em; --font-size-large: 2em; } .example { font-size: var(--font-size-small); } @media screen and (min-width: 800px) { .example { font-size: var(--font-size-large); } }

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

 .example { --example-font-size: 1.2em; } @media screen and (min-width: 800px) { .example { --example-font-size: 2em; } }

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

 .example { font-size: var(--example-font-size); }

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

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

Адаптивный дизайн с пользовательскими свойствами

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

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

Если он меняется, это переменная

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

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

Отделите логику от дизайна

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

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

Логическая складка

Идея объявления переменных в верхней части документа или функции не нова. Это то, что мы делаем на большинстве языков, а теперь это то, что мы можем сделать и в CSS. Написание CSS таким образом создает четкое визуальное различие между CSS в верхней части документа и ниже. Мне нужен способ различать эти разделы, когда я говорю о них, и идея «логической складки» — это метафора, которую я начал использовать.
Над сгибом находятся все переменные препроцессора и пользовательские свойства. Сюда входят все различные значения, которые может иметь пользовательское свойство. Должно быть легко отследить, как изменяется пользовательское свойство.

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

Взгляните на действительно простой пример шестиколонной сетки flexbox:

 .row { --row-display: block; } @media screen and (min-width: 600px) { .row { --row-display: flex; } }

Пользовательское свойство --row-display изначально имеет значение block . При разрешении выше 600 пикселей режим отображения устанавливается на гибкий.

Ниже складка может выглядеть так:

 .row { display: var(--row-display); flex-direction: row; flex-wrap: nowrap; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6 { flex-grow: 0; flex-shrink: 0; } .col-1 { flex-basis: 16.66%; } .col-2 { flex-basis: 33.33%; } .col-3 { flex-basis: 50%; } .col-4 { flex-basis: 66.66%; } .col-5 { flex-basis: 83.33%; } .col-6 { flex-basis: 100%; }

Мы сразу знаем, что --row-display — это значение, которое изменяется. Изначально это будет block , поэтому значения flex будут игнорироваться.

Этот пример довольно прост, но если мы расширим его, включив в него столбец с изменяемой шириной, заполняющий оставшееся пространство, вероятно, значения flex-grow , flex-shrink и flex-basis нужно будет преобразовать в пользовательские свойства. Вы можете попробовать это или посмотреть более подробный пример здесь.

Пользовательские свойства для тем

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

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

Тематика обычно относится к тому, чтобы позволить пользователям каким-либо образом настраивать пользовательский интерфейс. Это может быть что-то вроде изменения цвета на странице профиля. Или это может быть что-то более локальное. Например, вы можете выбрать цвет заметки в приложении Google Keep.

Приложение Google Keep

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

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

Использовать глобальные динамические свойства

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

 :root { --THEME-COLOR: var(--user-theme-color, #d33a2c); }

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

Избегайте прямой установки глобальных динамических свойств

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

В приведенном выше примере значение --THEME-COLOR устанавливается равным значению --user-theme-color , если оно существует. Если --user-theme-color не задан, будет использоваться значение #d33a2c . Таким образом, нам не нужно предоставлять запасной вариант каждый раз, когда мы используем --THEME-COLOR .

В приведенном ниже примере вы можете ожидать, что фон будет green . Однако значение --user-theme-color не было установлено для корневого элемента, поэтому значение --THEME-COLOR не изменилось.

 :root { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }

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

Если мы хотим предоставить определенные свойства для наследования, мы можем заменить селектор :root на селектор * :

 * { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }

Теперь значение --THEME-COLOR пересчитывается для каждого элемента, поэтому можно использовать локальное значение --user-theme-color . Другими словами, цвет фона в этом примере будет green .

Вы можете увидеть несколько более подробных примеров этого шаблона в разделе «Управление цветом с помощью пользовательских свойств».

Обновление пользовательских свойств с помощью JavaScript

Если вы хотите установить пользовательские свойства с помощью JavaScript, есть довольно простой API, и он выглядит так:

 const elm = document.documentElement; elm.style.setProperty('--USER-THEME-COLOR', 'tomato');

Здесь я устанавливаю значение --USER-THEME-COLOR для элемента документа или, другими словами, для элемента :root , где оно будет унаследовано всеми элементами.

Это не новый API; это тот же метод JavaScript для обновления стилей элемента. Это встроенные стили, поэтому они будут иметь более высокую специфичность, чем обычный CSS.

Это означает, что легко применять локальные настройки:

 .note { --note-color: #eaeaea; } .note { background: var(--note-color); }

Здесь я установил значение по умолчанию для --note-color и применил его к компоненту .note . Я держу объявление переменной отдельно от объявления свойства, даже в этом простом примере.

 const elm = document.querySelector('#note-uid'); elm.style.setProperty('--note-color', 'yellow');

Затем я нацеливаюсь на конкретный экземпляр элемента .note и изменяю значение пользовательского свойства --note-color только для этого элемента. Теперь это будет иметь более высокую специфичность, чем значение по умолчанию.

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

Управление цветом с помощью пользовательских свойств

В дополнение к шестнадцатеричным значениям и именованным цветам, в CSS есть функции работы с цветами, такие как rgb() и hsl() . Они позволяют нам указать отдельные компоненты цвета, такие как оттенок или яркость. Пользовательские свойства можно использовать в сочетании с функциями цвета.

 :root { --hue: 25; } body { background: hsl(var(--hue), 80%, 50%); }

Это полезно, но некоторые из наиболее широко используемых функций препроцессоров — это расширенные цветовые функции, которые позволяют нам манипулировать цветом с помощью таких функций, как осветление, затемнение или обесцвечивание:

 darken($base-color, 10%); lighten($base-color, 10%); desaturate($base-color, 20%);

Было бы полезно иметь некоторые из этих функций в браузерах. Они появятся, но пока у нас нет встроенных функций изменения цвета в CSS, пользовательские свойства могут заполнить часть этого пробела.

Мы видели, что пользовательские свойства можно использовать внутри существующих функций цвета, таких как rgb() и hsl() , но их также можно использовать в calc() . Это означает, что мы можем преобразовать действительное число в процентное значение, умножив его, например, calc(50 * 1%) = 50% .

 :root { --lightness: 50; } body { background: hsl(25, 80%, calc(var(--lightness) * 1%)); }

Причина, по которой мы хотим сохранить значение яркости в виде реального числа, заключается в том, что мы можем манипулировать им с помощью calc перед преобразованием в проценты. Например, если я хочу затемнить цвет на 20% , я могу умножить его яркость на 0.8 . Мы можем сделать это немного проще для чтения, разделив вычисление легкости на пользовательское свойство с локальной областью видимости:

 :root { --lightness: 50; } body { --lightness: calc(var(--lightness * 0.8)); background: hsl(25, 80%, calc(var(--lightness) * 1%)); }

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

Упрощение темы

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

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

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

Использование пользовательских свойств сегодня

Даже если вы поддерживаете IE10 и 11, вы можете начать использовать настраиваемые свойства уже сегодня. Большинство примеров в этой статье связаны с тем, как мы пишем и структурируем CSS. Преимущества значительны с точки зрения удобства сопровождения, однако большинство примеров только уменьшают то, что в противном случае можно было бы сделать с более сложным кодом.

Я использую инструмент под названием postcss-css-variables, чтобы преобразовать большинство функций пользовательских свойств в статическое представление того же кода. Другие подобные инструменты игнорируют пользовательские свойства внутри медиа-запросов или сложных селекторов, обрабатывая пользовательские свойства так же, как переменные препроцессора.

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

Загрузка правильной таблицы стилей

Есть много способов использовать postCSS. Я использую процесс gulp для компиляции отдельных таблиц стилей для новых и старых браузеров. Упрощенная версия моей задачи gulp выглядит так:

 import gulp from "gulp"; import sass from "gulp-sass"; import postcss from "gulp-postcss"; import rename from "gulp-rename"; import cssvariables from "postcss-css-variables"; import autoprefixer from "autoprefixer"; import cssnano from "cssnano"; gulp.task("css-no-vars", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssvariables(), cssnano()])) .pipe(rename({ extname: ".no-vars.css" })) .pipe(gulp.dest("./dist/css")) ); gulp.task("css", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssnano()])) .pipe(rename({ extname: ".css" })) .pipe(gulp.dest("./dist/css")) );

В результате получается два файла CSS: обычный с пользовательскими свойствами ( styles.css ) и один для старых браузеров ( styles.no-vars.css ). Я хочу, чтобы IE10 и 11 обслуживались styles.no-vars.css и другими браузерами, чтобы получить обычный файл CSS.

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

Разумное использование другой таблицы стилей и предотвращение мелькания нестилизованного контента — непростая задача. Если вам не нужны динамические функции настраиваемых свойств, вы можете рассмотреть возможность обслуживания всех браузеров styles.no-vars.css и использования настраиваемых свойств просто в качестве инструмента разработки.

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

 <head> <style> /* inlined critical CSS */ </style> <script> loadCSS('non-critical.css'); </script> </head>

Мы можем расширить это, чтобы загрузить либо styles.css либо styles.no-vars.css , в зависимости от того, поддерживает ли браузер пользовательские свойства. Мы можем обнаружить поддержку следующим образом:

 if ( window.CSS && CSS.supports('color', 'var(--test)') ) { loadCSS('styles.css'); } else { loadCSS('styles.no-vars.css'); }

Заключение

Если вы изо всех сил пытаетесь эффективно организовать CSS, испытываете трудности с адаптивными компонентами, хотите внедрить темы на стороне клиента или просто хотите правильно начать с пользовательских свойств, это руководство расскажет вам все, что вам нужно знать.

Все сводится к пониманию разницы между динамическими и статическими переменными в CSS, а также к нескольким простым правилам:

  1. Отделите логику от дизайна;
  2. Если свойство CSS изменяется, рассмотрите возможность использования пользовательского свойства;
  3. Измените значение настраиваемых свойств, а не то, какое настраиваемое свойство используется;
  4. Глобальные переменные обычно статические.

If you follow these conventions, you will find that working with custom properties is a whole lot easier than you think. This might even change how you approach CSS in general.

Дальнейшее чтение

  • “It's Time To Start Using Custom Properties,” Serg Hospodarets
    A general introduction to the syntax and the features of custom properties.
  • “Pragmatic, Practical, And Progressive Theming With Custom Properties,” Harry Roberts
    More useful information on theming.
  • Custom Properties Collection, Mike Riethmuller on CodePen
    A number of different examples you can experiment with.