Подводные камни пользовательских CSS-свойств

    Автор курса Нетологии «HTML-верстка» Стас Мельников рассказал о нюансах, которые могут усложнить работу с пользовательскими CSS-свойствами.

    Правила синтаксиса названий пользовательских свойств


    Мы привыкли, что встроенные CSS-свойства нечувствительны к регистру. Поэтому следующие способы объявления свойства border дадут одинаковый результат:

    button {
      BORDER: 2px solid #800080;
    }
    
    button {
      border: 2px solid #800080;
    }

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

    button {
      --NETOLOGY-BRAND-COLOR: #800080;
      --netology-brand-color: #27ae60;
        
      border: 2px solid var(--NETOLOGY-BRAND-COLOR);
      color: var(--netology-brand-color);  
    }



    Теперь рамка стала цветом #800080 (фиолетовый), а цвет текста — #27ae60 (зеленый).

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


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

    .element::before {
      --color: rgba(0, 0, 0, 1);
      --hex: #000000;
      --value: 20px;
      --number: 3;
      --text: "Hey, what's up?";
      --keyword: currentColor;
    }

    Для примера зададим рамку с цветом #800080 для кнопки:

    button {
      --netologyBrandColor: #800080;
      border-width: 2px;
      border-style: solid;
      border-color: var(--netologyBrandColor);
    }



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

    button {
      --netologyBrandColor: #800080;
      --buttonBorderColor: var(--netologyBrandColor);
      border-width: 2px;
      border-style: solid;
      border-color: var(--buttonBorderColor);
    }

    Результат ничем отличается от предыдущего, и у кнопки все так же рамка с цветом #800080.

    Если у пользовательского свойства --netologyBrandColor будет некорректное значение для свойства border-color, например 18px, то рамка станет черной.  

    button {
      --netologyBrandColor: 18px;  
      --buttonBorderColor: var(--netologyBrandColor);
      border-width: 2px;
      border-style: solid;
      border-color: var(--buttonBorderColor);
    }



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

    В нашем случае 18px некорректное значение для встроенного свойства border-color, и поэтому браузер установит значение по умолчанию, т.е currentColor. Это очень легко проверить, задав для свойства color значение #800080:

    button {
      --netologyBrandColor: 18px;  
      --buttonBorderColor: var(--netologyBrandColor);
      border-width: 2px;
      border-style: solid;
      border-color: var(--buttonBorderColor);
      color: #800080;
    }



    Как видим, браузер применил значение #800080 для рамки. В этом примере я использовал полный синтаксис, чтобы установить рамку. Но мы можем использовать краткий, а именно свойство border.

    button {
      --netologyBrandColor: 18px;  
      --buttonBorderColor: var(--netologyBrandColor);
      border: 2px solid var(--buttonBorderColor);
      color: #800080;
    }



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

    В нашем примере у встроенного свойства border в качестве одного из значений установлено пользовательское свойство --buttonBorderColor с некорректным значением 18px. Согласно описанному алгоритму, браузер просто проигнорировал свойство border, поэтому у элемента пропала рамка.

    Значение по умолчанию для пользовательских свойств


    Когда мы рассматривали пример с функцией var, то использовали только один параметр — название пользовательского свойства.

    button {
      --netologyBrandColor: #800080;
      border: 2px solid var(--netologyBrandColor);
      color: var(--netologyBrandColor);
    }
    
    button:hover {
      --netologyBrandColor: #27ae60;
    }

    Но кроме него, функция var может принимать и второй — значение по умолчанию. Для объявления значения по умолчанию нужно после названия пользовательского свойства поставить запятую и написать само значение.


    Когда браузер поймет, что разработчик не объявил значение для пользовательского свойства, то использует значение по умолчанию. Например значение #800080 (фиолетовый) для пользовательского свойства --netologyBrandColor.

    button {
      border: 2px solid var(--netologyBrandColor, #800080);
      color: var(--netologyBrandColor, #800080);
    }
    
    button:hover {
      --netologyBrandColor: #27ae60;
    }
    



    Мы видим, что рамка и текст стали фиолетовыми. Но если навести на кнопку, то они станут зелеными. Это говорит о том, что браузер применил значение #27ae60 для пользовательского свойства, тем самым заменив значение по умолчанию.

    Но это еще не все возможности функции var. В предыдущим примере пользовательское свойство --netologyBrandColor используется два раза, соответственно, поэтому я установил два раза значение по умолчанию.

    Но можно сделать по-другому. Функция var позволяет передавать другую функцию var, поэтому в качестве значения по умолчанию можно задать другое пользовательское свойство. Я перепишу предыдущий пример с использованием пользовательского свойства --defaultButtonColor:

    button {
      --defaultButtonColor: #800080;
      
      border: 2px solid var(--netologyBrandColor, var(--defaultButtonColor));
      color: var(--netologyBrandColor, var(--defaultButtonColor));
    }
    
    button:hover {
      --netologyBrandColor: #27ae60;
    }

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

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


    В CSS работает механизм наследования, который позволяет элементам наследовать свойства у родительских элементов. В этом плане пользовательские свойства ничем не отличаются от них. Для примера я напишу код, в котором пользовательское свойство --netologyBrandColor наследуется от родительского элемента body.

    body {
      --netologyBrandColor: #800080;
    }
    
    button {
      border: 2px solid var(--netologyBrandColor);
      color: var(--netologyBrandColor);
    }
    </td></tr>



    Посмотрев в инспектор, можно заметить надпись «Inherited from body», которая показывает нам, что пользовательское свойство было взято из элемента body. Но если для элемента button добавить пользовательское свойство --netologyBrandColor, то оно уже перекроет свойство из элемента body.

    body {
      --netologyBrandColor: #800080;
    }
    
    button {
      --netologyBrandColor: #27ae60;
      border: 2px solid var(--netologyBrandColor);
      color: var(--netologyBrandColor);
    }



    В инспекторе видно, что пользовательское свойство --netologyBrandColor у элемента button переопределило свойство --netologyBrandColor, которое мы указали для элемента body.

    Глобальные значения


    В стандарте CSS Custom Properties ввели особенный псевдокласс root, который позволяет указать пользовательские свойства, которые применяются к корневому элементу документа. Например, в HTML-документе к элементу html.

    :root {
      --netologyBrandColor: #800080;
    }
    
    button {
      border: 2px solid var(--netologyBrandColor);
      color: var(--netologyBrandColor);
    }



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

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 28" width="50" height="50">
        <style>
            :root{
                --iconColor: #ffcc00;
                --iconStroke: #000000;
                --iconStrokeWidth: 2px;
            }
        </style>
        <path stroke="var(--iconStroke)" 
              stroke-width="var(--iconStrokeWidth)" 
              fill="var(--iconColor)" 
              d="M26 10.109c0 .281-.203.547-.406.75l-5.672 5.531 1.344 7.812c.016.109.016.203.016.313 0 .406-.187.781-.641.781a1.27 1.27 0 0 1-.625-.187L13 21.422l-7.016 3.687c-.203.109-.406.187-.625.187-.453 0-.656-.375-.656-.781 0-.109.016-.203.031-.313l1.344-7.812L.39 10.859c-.187-.203-.391-.469-.391-.75 0-.469.484-.656.875-.719l7.844-1.141 3.516-7.109c.141-.297.406-.641.766-.641s.625.344.766.641l3.516 7.109 7.844 1.141c.375.063.875.25.875.719z"/>
    </svg>



    И здесь мы видим, что у корневого элемента SVG добавился псевдокласс root. Соответственно, это доказывает, что псевдокласс root применяется не только к тегу html, а к любому корневому элементу.

    Заключение


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

    От редакции


    Курсы «Нетологии» по теме:

    Нетология
    79.45
    Университет интернет-профессий
    Share post

    Comments 17

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

      Фраза «корректное CSS-значение» как-то двояко интерпретируется. Не просто любое, а почти произвольный набор почти любых токенов. Тот же стандарт в качестве примера приводит --foo: if(x > 5) this.width = 10;, который валидный, хоть и в целом бесполезный

        0
        Я бы еще добавил, что свойства нельзя определить через самих себя, т.е. вот такое задание значения по умолчанию (если не унаследовалось) не работает:
        --color: var(--color, #abcdef);
          0
          Так самый главный вопрос: насколько это применимо в реальной жизни, у каких пользователей и на каких платформах это НЕ будет работать? Каково реальное соотношение тех, у кого это будет работать и тех, у кого — нет?
            +1
              0
              Спасибо за пояснение, тогда ещё один вопрос: в популярных проектах это уже используется в массовом порядке или разработчики пока этим не пользуются? Я бы с удовольствием перевёл свой проект

              https://hi-lab.ru/arduino-mega-server

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

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


                    Или можно добавить конкретные значения как fallback для старых браузеров, а для новых переменные тоже оставить — так умеет делать postcss-custom-properties

                      0
                      Именно в перемене на лету и заключен весь смысл нативных css-переменных. Только для динамики они и используются. Если достаточно статичных значений, все используют препроцессоры.
                        0

                        Зачем плодить сущности? Я использую CSS-переменные только для статики и прекрасно живу без всяких препроцессоров, postcss более чем достаточно для моих задач.

                          0
                          Ну можно и микроскопом гвозди забивать.
                          Препроцессор нужен и полезен в любом случае, так что как раз он и не плодит лишние сущности.
                            0

                            Огласите весь список полезностей, пожалуйста? Абсолютно все фичи, которые мне нужны были от less и scss, я нашёл и использую в postcss — переменные в том числе. С тех пор нужность less, scss и прочих мне непонятна.

                              0
                              Модульность, вложенность, переменные, вычисления, интерполяция, миксины, условия, циклы, функции (как стандартные, например для цветов, так и пользовательские).
                                0
                                Ну, как минимум три четверти из этого есть в postcss и я это активно пользую. Цветами/функциями пользоваться не доводилось, но беглый гуглинг намекает, что и это есть
                                  0
                                  Всё есть. В виде зоопарка плагинов от разных авторов, с нестандартным и неконсистентным синтаксисом. А можно просто использовать один цельный язык.
                                    0
                                    Всё есть. В виде зоопарка плагинов от разных авторов, с нестандартным и неконсистентным синтаксисом
                                    Как будто что-то плохое
            0

            Отличная статья! Но глаз зацепился за досадную неточность: псевдокласс :root не «ввели в стандарте CSS Custom Properties», а был с самого начала в стандарте CSS-селекторов 3 уровня. Впрочем, для объявления глобальных переменных, действительно, он пришёлся как нельзя кстати)

            Only users with full accounts can post comments. Log in, please.