CSS: Современные селекторы псевдоклассов
Селекторы псевдокласса — это те, которые начинаются с символа двоеточия ":" и соответствуют состоянию текущего элемента. Состояние может быть относительно дерева документа или реакцией на изменение состояния, например :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
input
Корректно удаляется стиль :focus
, когда фокус переместиться на элемент с помощью мыши, применится :focus-visible
, что приведёт к смене цвета границы и скрытию обводки при переключении с клавиатуры.
button
Используется не только :focus-visible
без дополнительного правила button:focus:not(:focus-visible)
, которое удаляет обводку при :focus
, но и отобразиться box-shadow
при вводе с клавиатуры.
Safari
input
Продолжает использовать только :focus
стили.
button
Кажется, теперь частично используется :focus-visible
на кнопке, скрывая стили :focus
при клике мышью, но по-прежнему показывая стили :focus
при переключении с клавиатуры.
Итак, на данный момент рекомендуется продолжать включать в стили :focus
, постепенно переходя на :focus-visible
. Далее идёт пример с CodePen, с которым вы можете поэкспериментировать:
Селектор псевдокласса :focus-within
Селектор псевдокласса :focus-within
поддерживается всеми современными браузерами и действует почти как родительский селектор, но только для очень специфических условий. Когда присоединяется к элементу содержащему дочерний элемент соответствующий :focus
, стили добавляются элементу и/или другим элементам в контейнере.
Практическое применение для использования этого поведения — стилизация label
формы, когда связанный с ней input
находится в фокусе. Что бы это заработало, мы помещаем label
и input
в контейнер, а затем прикрепляем :focus-within
к этому контейнеру, а так же выбираем метку:
.form-group:focus-within label {
color: blue;
}
В результате цвет label
станет синим, когда input
окажется в фокусе. Следующий пример это продемонстрирует:
Селектор псевдокласса :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;
}
В этом примере, взятом из стилей моего проекта 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()
с правилами указанными выше:
Улучшенный селектор псевдокласса :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
.
Бонус. Предыдущая демонстрация также включает пример цепочки :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 есть ещё много селекторов, включая псевдоклассы. Вот ещё несколько мест где вы можете узнать о них:
- Документация MDN по CSS селекторам включающая исчерпывающий список [Ru].
- Руководство в двух частях по продвинутым CSS селекторам, можете начать с первой части [Eng].
- Получить удовольствие от изучения CSS селекторов с игрой CSS Diner.
- Kitty Giraudel создала инструмент объяснения селектора, который разбивает и описывает части предоставленного селектора.