CSS: Современные селекторы псевдоклассов

Источник: «A Guide To Newly Supported, Modern CSS Pseudo-Class Selectors»
Проект черновика рабочей группы CSS для Selectors Level 4 включает несколько селекторов псевдоклассов, которые уже поддерживаются в большинстве современных браузеров. В этом руководстве будут рассмотрены те из них, которые имеют наилучшую поддержку, а так же приведены примеры, демонстрирующие как вы можете их использовать уже сейчас.

Селекторы псевдокласса — это те, которые начинаются с символа двоеточия ":" и соответствуют состоянию текущего элемента. Состояние может быть относительно дерева документа или реакцией на изменение состояния, например :hover или :checked.

Селектор псевдокласса :any-link

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

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

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

:any-link {
color: purple;
}

a {
color: red;
}

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

Селектор псевдокласса :focus-visible

Могу поспорить, что одним из наиболее распространённых нарушений доступности в интернете, это удаление outline интерактивных элементов, таких как ссылок, кнопок и полей ввода форм для их состояния :focus. Одна из основных целей outline — служить визуальным индикатором для пользователей использующих для навигации клавиатуру. Состояние видимого фокуса критически важно в качестве инструмента поиска путей, поскольку эти пользователи переключаются между элементами интерфейса, и помогает акцентироваться на интерактивном элементе. В частности, видимый фокус описывается в WCAG Success Criterion 2.4.11: Focus Appearance (Minimum).

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

Примечание. Дополнительные сведения см. в рабочем проекте спецификации :focus-visible.

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

Присвоим кнопке и полю текстового ввода следующие стили, и посмотрим, что произойдёт:

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;
}

Firefox и Chromium

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

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

Safari

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

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

Итак, на данный момент рекомендуется продолжать включать в стили :focus, постепенно переходя на :focus-visible. Далее идёт пример с CodePen, с которым вы можете поэкспериментировать:

See the Pen

Селектор псевдокласса :focus-within

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

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

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

В результате цвет label станет синим, когда input окажется в фокусе. Следующий пример это продемонстрирует:

See the Pen

Селектор псевдокласса :is()

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

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

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

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

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

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

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

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

p:is(article > *)

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

:matches(h1, h2, h3) { }

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

:is(h1, h2, h3) { }

Следует также упомянуть, что наряду с новыми селекторами есть обновлённый вариант @support - @support selector. Так же доступный как @support not selector.

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

Вы можете проверять поддержку селектора псевдокласса :is() с помощью чего-то вроде следующего, но на самом деле вы лишаетесь поддержки Safari, поскольку Safari поддерживает :is(), но не поддерживает @support selector.

@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() в силу его природы. У него лучшая поддержка чем у @support, поэтому попытка использовать его для создания запасного варианта вряд ли принесёт большой выигрыш. По сути, помните о невозможности правильно создать резервные правила для :where() и внимательно проверьте свои собственные данные, чтобы определить для вашей уникальной аудитории.

Вы можете сами протестировать :where() с правилами указанными выше:

See the Pen

Улучшенный селектор псевдокласса :not()

Базовый селектор псевдокласса :not() поддерживается начиная с Internet Explorer 9. Но Selectors Level 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() так же может содержать ссылку на базовый селектор в качестве потомка с использованием *. Следующий пример демонстрирует эту способность, выбирая ссылки, которые не являются потомками nav.

See the Pen

Бонус. Предыдущая демонстрация также включает пример цепочки :not() и :is() для выбора изображений, которые не являются соседями элементов h2 или h3.

Предлагается, но "с риском" - :has()

Последний псевдокласс, который представляет собой очень интересное предложение, но на данный момент не реализованное даже в "экспериментальных функциях" браузеров, это - :has(). Фактически, он внесён в черновик Selector Level 4, как "с риском", что означает признание испытывающим трудности с завершением его внедрения, и поэтому он может быть исключён из рекомендации.

В случае реализации :has(), по сути будет "родительским селектором", которые многие разработчики CSS стремились сделать доступным. Он будет работать с логикой, аналогичной комбинации обоих "focus-within() и :is() с дочерними селекторами, где вы ищите наличие потомков, но применённый стиль будет относиться к родительскому элементу.

Согласно следующему правилу, если бы nav содержал button, тогда бы в nav уменьшились верхний и нижний padding:

nav {
padding: 0.75rem 0.25rem;

nav:has(button) {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}

Опять же, это не реализовано ни в одном современном браузере даже экспериментально - но об этом интересно подумать! Робин Рендел (Robin Rendle) предоставил дополнительную информацию об этом будущем селекторе на CSS-Tricks

Почётное упоминание из Level 3: :empty

Полезный псевдокласс, который вы, возможно, пропустили из Selectors Level 3 - это :empty, соответствующий элементу без дочерних элементов, включает текстовые узлы.

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

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

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

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

Ресурсы для изучения селекторов

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

Дополнительные материалы

Предыдущая Статья

Новое в Symfony 5.4: Различные улучшения (часть 3)

Следующая Статья

JavaScript: Что такое hoisting