Руководство по новым поддерживаемым, современным CSS селекторам псевдоклассов

alexei06/09/2021 - 09:06
Руководство по новым поддерживаемым, современным CSS селекторам псевдоклассов

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

Селекторы псевдоклассов – это такие селекторы, которые начинаются с символа двоеточия ":" и соотносятся с состоянием элемента. Они могут относиться как к элементам в дереве документа, так и с изменением их состояния, например :hover или :checked.

:any-link

Хотя данный псевдокласс определен в селекторах уровня 4, он уже довольно давно имеет кроссбраузерную поддержку. Псевдокласс :any-link соотносится с якорной гиперссылкой, если у нее есть атрибут href. При этом он является эквивалентом сразу двух псевдоклассов ссылки - :link и :visited. Таким образом, если вам нужно определить базовые свойства, такие как color, независимо от статуса посещенности ссылки, то данный псевдокласс поможет уменьшить ваши стили на один селектор.


:any-link {
  color: blue;
  text-underline-offset: 0.05em;
}

Важным замечанием о специфичности данного селектора является то, что :any-link имеет более высокий приоритет как селектор по отношению к селектору a, даже если последний расположен ниже в каскадной таблице стилей, так как он имеет специфичность класса. В следующем примере ссылки будут фиолетовыми:


:any-link {
  color: purple;
}

a {
  color: red;
}

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

:focus-visible

Одним из наиболее распространенных нарушений принципа доступности в Интернете является удаление свойства outline у интерактивных элементов, таких как ссылки, кнопки и поля ввода форм, когда они находятся в состоянии фокуса - :focus. При этом одной из основных задач свойства outline - это визуальная индикация для пользователей, которые используют клавиатуру для навигации по странице. Состояние видимого фокуса имеет решающее значение в качестве инструмента поиска путей, поскольку эти пользователи переходят по элементам интерфейса, нажимая кнопку Tab. При этом это состояние помогает усилить интерактивные элементы.

Псевдокласс :focus-visible предназначен только для того, чтобы показывать кольцо фокусировки, когда пользовательский агент с помощью эвристики определяет, что оно должно быть видимым. Другими словами, браузеры будут определять, когда применять :focus-visible, основываясь на таких вещах, как метод ввода, тип элемента и контекст взаимодействия. При тестировании на настольном компьютере с клавиатурой и мышью вы должны увидеть заданные стили :focus-visible, когда вы используете клавишу Tab для перехода на интерактивный элемент, но не при нажатии на него кнопкой мыши. Исключение касается только текстовых полей вводов и текстовых областей, которые должны отображать стили :focus-visible для всех типов ввода фокуса.

Похоже, что в настоящее время последние версии браузеров Firefox и Chromium обрабатывают псевдокласс :focus-visible для элементов ввода форм в соответствии со спецификацией, в которой говорится, что UA должен удалять стили :focus, когда есть соответствующие стили :focus-visible. Safari пока не поддерживает :focus-visible, поэтому, чтобы избежать удаления свойства outline, необходимо предоставить стили :focus для обратной совместимости.

Возьмем кнопку и текстовое поле ввода со следующим набором стилей и давайте посмотрим, что произойдет:


input:focus,
button:focus {
  outline: 2px solid blue;
  outline-offset: 0.25em;
}

input:focus-visible {
  outline: 2px solid transparent;
  border-color: blue;
}

button:focus:not(:focus-visible) {
  outline: none;
}

button:focus-visible {
  outline: 2px solid transparent;
  box-shadow: 0 0 0 2px #fff, 0 0 0 4px blue;
}

CHROMIUM И FIREFOX

  • input

    Когда элемент получает фокус при помощи мыши, корректно удаляет стили :focus в пользу :focus-visible, что приводит к изменению свойства border-color и прячет outline при вводе с клавиатуры.

  • button

    Не только использует :focus-visible без дополнительного правила для button:focus:not(:focus-visible), которое удаляет outline для :focus, но и сделает видимым box-shadow только при вводе с клавиатуры.

SAFARI

  • input

    Продолжает использовать только стили :focus.

  • button

    В настоящее время частично применяется :focus-visible, скрывая стили :focus при нажатии кнопкой мыши, однако при использовании клавиатуры все еще применяются стили :focus.

Таким образом на данный момент рекомендуется продолжать включать стили :focus, а затем постепенно улучшать их до использования :focus-visible.

:focus-within

Псевдокласс :focus-within поддерживается всеми современными браузерами и действует почти как родительский селектор, но только для очень специфических условий. При использовании с элементом-контейнером, когда у его дочернего элемента возникает состояние фокуса, то стили могут быть добавлены к контейнеру и/или любым другим элементам в контейнере.

На практике это можно использовать, например, для определения стиля метки формы, когда форма получит фокус. Для этого нужно поместить метку и поле ввода внутри контейнера, а затем прикрепить к метке внутри контейнера :focus-within:


.form-group:focus-within label {
  color: blue;
}

В результате, когда поле ввода получит фокус, метка станет синего цвета.

:is()

Псевдокласс :is() может принимать список селекторов для сопоставления. Например, вместо того, чтобы перечислять стили заголовков по отдельности, вы можете сгруппировать их под селектором :is(h1, h2, h3).

Вот несколько уникальных особенностей поведения селектора :is():

  • Если указанный в списке селектор недействителен, правило будет по-прежнему действовать для допустимых селекторов. Например, в селекторе :is(-ua-invalid, article, p) правило будет действовать для article и p.
  • Вычисленная специфичность будет такой же как специфичность переданного селектора с самой высокой специфичностью. Например, :is(#id, p) будет иметь специфичность как у #id, т.е. 1.0.0, в то время как :is(p, a) будет иметь специфичность 0.0.1.

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


:is(h1, h2, h3) {
  line-height: 1.2;
}

:is(h2, h3):not(:first-child) {
  margin-top: 2em;
}

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

Кроме того, при подключении к другому селектору вы можете проверять, совпадает ли базовый селектор с селектором-потомком внутри псевдокласса :is(). Например, следующее правило выбирает только те абзацы, которые являются прямыми потомками статей. Универсальный селектор используется в качестве ссылки на базовый селектор p.


p:is(article > *)

Для лучшей текущей поддержки, если вы хотите начать использовать данный псевдокласс, вам также потребуется удвоить количество стилей, включив дублирующие правила с использованием :-webkit-any() и :matches(). Не забудьте определить эти отдельные правила, иначе даже поддерживающие браузеры отбросят заданные стили! Другими словами, не забывайте определять все три правила:


:matches(h1, h2, h3) { }

:-webkit-any(h1, h2, h3) { }

:is(h1, h2, h3) { }

Стоит сказать, что наряду с самими новыми селекторами есть еще и обновленная версия правила @supports, которая имеет вид @supports селектор. Также, есть вариант @supports not селектор.

Примечание: В настоящее время (из современных браузеров) только Safari не поддерживает данное @-правило.

Вы можете проверить наличие поддержки псевдокласса :is() следующим образом, но при этом вы потеряете поддержку в Safari, поскольку Safari поддерживает :is(), но не поддерживает @supports селектор:


@supports selector(:is(h1)) {
  :is(h1, h2, h3) {
    line-height: 1.1;
  }
}

:where()

Псевдокласс :where() почти идентичен псевдоклассу :is() за исключением одного важного отличия: он всегда имеет нулевую специфичность. Это открывает невероятные возможности для разработчиков различных фреймворков, тем и систем проектирования. Используя :where(), разработчик системы или темы может устанавливать значения по умолчанию, а последующие разработчики могут включать переопределения или расширения без конфликтов специфичности.

Рассмотрим следующий набор стилей для селектора img. При использовании :where(), даже с селектором более высокой специфичности, специфичность остается нулевой. В следующем примере, как вы думаете, какого цвета будет рамка у изображения?


:where(article img:not(:first-child)) {
    border: 5px solid red;
}

:where(article) img {
  border: 5px solid green;
}

img {
  border: 5px solid orange;
}

Первое правило имеет нулевую специфичность, поскольку оно полностью находится внутри псевдокласса :where(). Таким образом, в сравнении со вторым правилом побеждает второе правило. Добавленный селектор-элемент img в качестве последнего стиля в соответствии с правилами каскада получает главный приоритет. Это связано с тем, что у него такая же специфичность как и у правила :where(article) img, поскольку часть с псевдоклассом :where() никак не увеличивает специфичность.

Использование :where() наряду со стилями для обратной совместимости вызывает некоторые трудности из-за особенности нулевой специфичности, так как эта особенность, вероятно, может сталь главной причиной, по которой вы захотите отдать предпочтение этому псевдоклассу вместо псевдокласса :is(). И если вы добавите стили, предназначенные для обратной совместимости, они, скорее всего, победят стили с псевдоклассом :where() в силу природы последнего. При этом у него лучшая общая поддержка, чем у @supports селектор, таким образом, попытка использовать его для создания стилей обратной совместимости вряд ли даст большую (если вообще даст) выгоду. В принципе, для эффективного использования данного псевдокласса достаточно осознавать невозможность правильного создания стилей для обратной совместимости с :where() и тщательно проверять свои собственные данные, чтобы определить безопасно ли начинать использовать их для вашей уникальной аудитории.

Улучшенный :not()

Базовые свойства селектора :not() поддерживаются начиная с Internet Explorer 9. Но Селекторы 4-го уровня улучшают :not(), позволяя ему принимать список селекторов, точно так же, как :is() и :where().

Следующие правила дадут одинаковые результаты в поддерживаемых браузерах:


article :not(h2):not(h3):not(h4) {
  margin-bottom: 1.5em;
}

article :not(h2, h3, h4) {
  margin-bottom: 1.5em;
}

Способность псевдокласс :not() принимать список селекторов хорошо поддерживается современными браузерами.

Как и в случае с :is(), улучшенный :not() также может принимать ссылку на базовый селектор в качестве последующего использования *.

:empty

И в заключение стоит упомянуть полезный псевдокласс из предыдущей спецификации Селекторов уровня 3, который вы, возможно, пропустили - :empty. Данный псевдокласс соответствует элементу, у которого нет дочерних элементов, включая текстовые узлы.

Так, правило p:empty будет соответствовать элементу <p></p>, но не <p>Привет</p>.

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


.search-results:empty {
  display: none;
}

Возможно, вы подумывали о том, чтобы добавить сообщение в элемент в пустом состоянии, и у вас возникает соблазн добавить его с помощью этого псевдоэлемента и свойства content. Но здесь есть один подводный камень, который заключается в том, что пользователям вспомогательных технологий могут быть недоступны сообщения в свойстве content. Другими словами, чтобы сообщение типа "нет результатов" было доступно, вам стоит добавить его в качестве реального элемента, такого как абзац (aria-label не доступен для скрытого div).