CSS-live.ru

Явный контроль над специфичностью в CSS?

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

В ишью на гитхабе Лия обратила внимание на то, что принцип «более конкретные CSS-правила переопределяют более общие» — всего лишь эвристика. В большинстве простых случаев она помогает, но нередко эта эвристика дает сбой, и тогда приходится прибегать к хакам типа повторения одного класса в селекторе несколько раз или добавления лишних :not(). Лия предлложила вместо этих хаков ввести новый функциональный псевдокласс (например, :filter()), похожий на :matches() из селекторов 4 уровня, но c нулевой специфичностью (не меняющий специфичность остальной части селектора).

Идея понравилась Табу Аткинсу, который тут же развил ее: он предложил псевдокласс :is() с двумя аргументами. Первый аргумент — произвольный селектор (как и у :matches()), а второй — набор чисел, явным образом задающий желаемую специфичность (например, :is(.foo, 1 2 3) имел бы специфичность 1 ID, 2 классов и 3 тегов). По умолчанию (без второго аргумента) специфичность равнялась бы нулю. Плюсом этой идеи оказалось то, что ее можно внедрять поэтапно (например, в спецификацию селекторов 4 уровня добавить только вариант с одним аргументом, «обнуляющий» специфичность, а второй аргумент перенести в модуль 5 уровня), чтобы браузерам было проще реализовать ее хоть в каком-то виде.

Другие участники обсуждения предлагали свои варианты. Филип Уолтон (у которого свои давние «счеты» со специфичностью:) предложил «отключать» специфичность на уровне CSS-файла (например, в виде атрибута nospecificity для <link>). А Роман Комаров — автор способа переписать практически любой селектор так, чтобы его итоговая специфичность была как у одного класса, с помощью обновленного :not() из селекторов 4 уровня — предложил вариант создавать свои собственные «слои» («origins» в терминах спецификации CSS Cascade) вдобавок к существующим «авторским», «браузерным», «пользовательским» и т.д. стилям. Хотя это скорее дополнение к селектору :is(), а не замена ему. Наконец, Франсуа Реми предложил свой вариант названия для нового псевдокласса — :when(), в чем-то более логичный (например, button:when(:hover) или input:when([disabled])).

А вы что думаете? Нужен ли нам такой контроль над специфичностью или это лишь увеличит путаницу вокруг нее? И если нужен, то в каком виде? Подключайтесь к мозговому штурму!

Добавлено 02.12.2017: псевдокласс :is() только что добавили в черновик спецификации!

Добавлено 02.02.2018: название :is() забраковали из-за риска путаницы, что из них — противоположность :not(). Над новым названием пока думают. Присоединяйтесь к обсуждению, ваш аргумент может оказаться решающим!

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

10 комментариев

  1. Интересный у Комарова способ…

    :not(:not(.bar > *), :not(.foo)), насколько я понял, работает, в связи с тем, что получается логическое «и», между любой прямой потомок класса bar, и классом .foo, получаем
    прямой потомок класса bar, являющийся одновременно с этим классом .foo, что и есть:
    .bar > .foo, но со специфичностью класса. Ну и так усложнять можно, по-идее, сколько угодно, главное выбрать способ записи такой, чтоб хоть как-то логика прослеживалась, в центре самые «верхние» родительские условия, а дальше слоями добавлять потомков или иные условия. Получается эдакий каскад наоборот :)
    Бикус в том, что у :not такая же специфичность, как у самого «специфичного» аргумента, и, если использовать классы, то любая «классная» конструкция будет иметь специфичность класса.

  2. Eсли что-то подобное примут, то тот же .bar > .foo,
    можно будет писать, что-то вроде:
    :matches(.bar > .foo, as 0, 1, 0) {…}
    Тут вот что мне кажется существенным. Идентификатор означает единственность, некая исключительность, которая, по-умолчанию, ведёт к увеличению специфичности. В каких ситуациях может быть актуально, чтоб исключительность не вела к тому, что его условие было приоритетным?
    Чисто теоретически, можно допустить ситуацию, когда идентификатор задаёт правила в каком-то определённом состоянии. А в другом состоянии это исключительность перестаёт быть актуальным, например, при наведении… Но это гипотетический пример, в реальной жизни я себе плохо представляю стоит ли оно того…

    1. Матчес я тут немного от фонаря привёл, как один из возможных способов его использования…

  3. Но вообще как-минимум одна ситуация, где мне нужен контроль над специфичностью я сталкивался.
    А именно всеми любимая тема отмены отступа между инлайн-блоками(http://css-live.ru/articles/zagadochnye-otstupy-mezhdu-inlajn-blokami.html)
    создаёшь класс .no_shift задаёшь ему font-size: 0; а его потомкам .no_shift > * font-size: 1rem. А дальше удивляешься почему у его потомков нужный размер шрифта не проставляется и пользуешься всякими несуразностями вроде .sup.sup . Вот тут бы сбросить специфичность для потомков бы не помешало…

    1. Вот ведь интересный способ — делишься с общественностью, а в голову новые мысли приходят… Я тут вспомнил, можно же специфичность уменьшить через атрибут тега,
      div[class=»no_shift»] — если мне память не изменяет — должно работать =)

      1. К сожалению, у селектора по атрибуту специфичность такая же, как у класса (это с ID такой фокус срабатывает). А вообще что касается этого примера, тут, по-моему, проблема не в специфичности, а в наследовании. Может, есть смысл ввести возможность явно наследовать значения не только от непосредственного родителя, а от любого предка — скажем, сделав значение inherit функцией? Типа такого
        .no_shift > * { font-size: inherit(2); } /* наследование от предка на 2 уровня выше (т.е. от родителя для самого .noshift) */
        и/или такого?
        .no_shift > * { font-size: inherit(body); } /* наследование от предка, совпадающего с селектором — в данном случае body */

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

        1. Илья, согласен с Вами, что улучшать нужно способ, а не приспосабливать неправильный способ и думать что это самый лучший способ :-) Но идея с body всё равно интересная, как-то не рассматривал я раньше этот инхерит, видимо зря :)

           

          Что же касается флексбоксов, то всё-таки это, несколько «лёгкая» технология, которая «на автомате» «делает хорошо», но у неё не настолько обширные возможности выравнивания (а значит, вероятно, и более точного позиционирования элементов) как у гридов, на которые, с надеждой, и взираю…

          1. На всякий случай — это чисто гипотетические примеры возможного улучшения стандарта. То значение inherit, которое есть сейчас (в CSS Cascade Level 3/4) — не функция и позволяет наследовать только от непосредственного родителя. Но зато для любого свойства!

            1. Вообще, сама идея класса no_shift в том, чтоб восстанавливать для потомков некое значение по-умолчанию, скажем font-size: 1.6rem. Но нужно делать это так, чтоб класс у потомка имел больший приоритет, и когда мы назначим ему нужный размер шрифта, «общее правило» не перебивало его. Желательно, чтоб вся это конструкция имела некий «запас надёжности», поэтому «в коде ниже» не хочется…

              Я пока ничего лучшего чем, «двойное обращение к классу»( .font.font {…} ) не нашёл. Но это ж надо «вручную» отслеживать потомков у которых размер шрифта иной, чем по-умолчанию, что дискредитируют саму идею использования этого класса (no_shift), поскольку возможно проще, в этом случае, вручную назначать размер шрифта и для элемента и для потомков первого уровня.

              Ну или флексогридами…

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

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