Как работают CSS псевдоклассы :is, :where и :has

0 3

Селекторы в стилях применяются повсеместно. Пример ниже находит все параграфы и меняет жирность шрифта на bold:

CSS p { font-weight: bold; }

123 p {  font-weight: bold;}

Для поиска DOM узлов селекторы можно использовать и в JavaScript:

document.querySelector() возвращает первый найденный HTML элемент

document.querySelectorAll() возвращает все найденные HTML элементы в структуре похожей на массив NodeList

Псевдоклассы находят HTML элементы по их текущему состоянию. Наверное, самый известный псевдокласс — :hover. Он применяет стили к элементу, когда курсор наводится на него. Его используют для подсветки кликабельных ссылок и кнопок. Есть и другие популярные опции:

:visited: находит посещенные ссылки

:target: находит элементы, помеченные ссылкой

:first-child: находит первый дочерний элемент

:nth-child: выбирает заданный дочерний элемент

:empty: находит элемент без контента или без дочерних элементов

:checked: находит включенные чекбоксы и радиокнопки

:blank: стилизует пустые поля ввода

:enabled: находит активные input поля

:disabled: находит неактивные input поля

:required: находит input поля обязательные для заполнения

:valid: находит input поля с валидным введенным значением

:invalid: находит input поля с невалидным введенным значением

:playing: находит элементы, проигрывающие аудио или видео

CSS псевдокласс :is

Обратите внимание: в ранних версиях селектор писался по-другому — :matches() и :any(), но позже стандартом стал :is().

Очень часто нужно применять одинаковые стили ко множеству элементов. Например, текст внутри <p> должен быть черный, но внутри <article>, <section> или <aside> текст будет серый:

CSS /* default black */ p { color: #000; } /* gray in <article>, <section>, or <aside> */ article p, section p, aside p { color: #444; }

1234567891011 /* default black */p {  color: #000;} /* gray in <article>, <section>, or <aside> */article p,section p,aside p {  color: #444;}

Это простой пример, однако более сложные страницы усложнят и селекторы. Синтаксическая ошибка в одном селекторе поломает стили для всех элементов. CSS препроцессоры типа Sass разрешают использовать вложенность (что также появится в нативном CSS):

CSS article, section, aside { p { color: #444; } }

1234567 article, section, aside {   p {    color: #444;  } }

Этот CSS идентичен коду выше. Однако такой код писать легче, и он снижает вероятность ошибок. Но есть нюансы:

Пока эта функция не появилась в нативном CSS, придется использовать CSS билд инструменты. Можно использовать Sass, но это может усложнить работу в некоторых командах разработки.

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

:is() дает нам нативное CSS решение с поддержкой во всех современных браузерах (без IE):

CSS :is(article, section, aside) p { color: #444; }

123 :is(article, section, aside) p {  color: #444;}

Один селектор может содержать любое количество псевдоклассов :is(). Например, сложный селектор ниже применяет зеленый текст ко всем <h1>, <h2> и <p> элементам, которые расположены внутри <section> с классом .primary или .secondary. А также эти элементы должны быть не первым дочерним элементом <article>:

CSS article section:not(:first-child):is(.primary, .secondary) :is(h1, h2, p) { color: green; }

123 article section:not(:first-child):is(.primary, .secondary) :is(h1, h2, p) {  color: green;}

Аналогичный код без :is() требует 6 CSS селекторов:

CSS article section.primary:not(:first-child) h1, article section.primary:not(:first-child) h2, article section.primary:not(:first-child) p, article section.secondary:not(:first-child) h1, article section.secondary:not(:first-child) h2, article section.secondary:not(:first-child) p { color: green; }

12345678 article section.primary:not(:first-child) h1,article section.primary:not(:first-child) h2,article section.primary:not(:first-child) p,article section.secondary:not(:first-child) h1,article section.secondary:not(:first-child) h2,article section.secondary:not(:first-child) p {  color: green;}

Обратите внимание, :is() не может использоваться с псевдоэлементами ::before и ::after. Код ниже упадет с ошибкой:

CSS /* NOT VALID — selector will not work */ div:is(::before, ::after) { display: block; content: »; width: 1em; height: 1em; color: blue; }

12345678 /* NOT VALID — selector will not work */div:is(::before, ::after) {  display: block;  content: »;  width: 1em;  height: 1em;  color: blue;}

CSS псевдокласс :where

Синтаксис :where() аналогичен :is() и также поддерживается во всех современных браузерах (без IE). Очень часто результат его работы будет таким же, как у предыдущего псевдокласса. Например:

CSS :where(article, section, aside) p { color: #444; }

123 :where(article, section, aside) p {  color: #444;}

Разница в специфичности. Специфичность – алгоритм, определяющий приоритет перезаписи CSS селекторов. В примере ниже «article p» более специфичен чем просто «p». Поэтому все параграфы внутри article будут серыми:

CSS article p { color: #444; } p { color: #000; }

12 article p { color: #444; }p { color: #000; }

Для :is() специфичность равна самому специфичному селектору внутри. Для :where() специфичность равно нулю. Разберем CSS код ниже:

CSS article p { color: black; } :is(article, section, aside) p { color: red; } :where(article, section, aside) p { color: blue; }

1234567891011 article p {  color: black;} :is(article, section, aside) p {  color: red;} :where(article, section, aside) p {  color: blue;}

Применим этот код к HTML:

<article> <p>paragraph text</p> </article>

123 <article>  <p>paragraph text</p></article>

Текст будет красным, как показано в демо ниже:

:is() имеет одинаковую специфичность как и article p, но в коде псевдокласс расположен ниже, поэтому текст будет красным. Чтобы применить синий цвет, нужно удалить article p и :is(), ведь :where() наименее специфичный.

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

Пример CSS сброса ниже применяет top margin в 1em к заголовкам h2 за исключением ситуаций, когда заголовки являются первым дочерними элементами от article:

CSS /* CSS reset */ h2 { margin-block-start: 1em; } article :first-child { margin-block-start: 0; }

12345678 /* CSS reset */h2 {  margin-block-start: 1em;} article :first-child {  margin-block-start: 0;}

Если ниже попробовать применить свое значение для top margin у h2, у нас не получится, так как article :first-child имеет специфичность выше:

CSS /* never applied — CSS reset has higher specificity */ h2 { margin-block-start: 2em; }

1234 /* never applied — CSS reset has higher specificity */h2 {  margin-block-start: 2em;}

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

CSS /* styles now applied */ article h2:first-child { margin-block-start: 2em; }

1234 /* styles now applied */article h2:first-child {  margin-block-start: 2em;}

Также проблему решит !important для всех необходимых стилей, но не делайте так! Дальнейшее написание стилей и разработка станут только сложнее:

CSS /* works but avoid this option! */ h2 { margin-block-start: 2em !important; }

1234 /* works but avoid this option! */h2 {  margin-block-start: 2em !important;}

Лучше воспользоваться нулевой специфичностью :where() при сбросе:

CSS /* reset */ :where(h2) { margin-block-start: 1em; } :where(article :first-child) { margin-block-start: 0; }

12345678 /* reset */:where(h2) {  margin-block-start: 1em;} :where(article :first-child) {  margin-block-start: 0;}

Теперь можно переписывать любые стили из сброса, не обращая внимания на их специфичность. Больше не нужно писать !important:

CSS /* now works! */ h2 { margin-block-start: 2em; }

1234 /* now works! */h2 {  margin-block-start: 2em;}

CSS псевдокласс :has

Синтаксис :has() похож на :is() и :where(), но этот псевдокласс находит элемент, который содержит набор других элементов. Например, ниже стили, применяющие синюю рамку в 2 пикселя ко всем <a>, внутри которых есть один или более <img> или <section>:

CSS /* style the <a> element */ a:has(img, section) { border: 2px solid blue; }

1234 /* style the <a> element */a:has(img, section) {  border: 2px solid blue;}

Это самая классная доработка CSS за десятилетия! Разработчики, наконец, могут находить родительские элементы!

Неуловимый «родительский селектор» был одной из самых желанных фич в CSS, но он поставил перед вендорами браузеров вопрос производительности. Поэтому он долго откладывался. Проще говоря:

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

Добавление, удаление или изменение элементов в JS затрагивает стили всей страницы вплоть до закрывающего body.

Предполагается, что вендоры решили проблемы производительности, и :has() позволяет делать то, для чего раньше нужно было использовать JS. Например, можно указать стили внешнего тега формы fieldset и кнопки отправки, когда любое из обязательных полей заполнено неправильно:

CSS /* red border when any required inner field is invalid */ fieldset:has(:required:invalid) { border: 3px solid red; } /* change submit button style when invalid */ fieldset:has(:required:invalid) + button[type=’submit’] { opacity: 0.2; cursor: not-allowed; }

12345678910 /* red border when any required inner field is invalid */fieldset:has(:required:invalid) {  border: 3px solid red;} /* change submit button style when invalid */fieldset:has(:required:invalid) + button[type=’submit’] {  opacity: 0.2;  cursor: not-allowed;}

Как работают CSS псевдоклассы :is, :where и :has

Этот пример добавляет индикатор в подменю ссылок, которое содержит список дочерних элементов меню:

CSS /* display sub-menu indicator */ nav li:has(ol, ul) a::after { display: inlne-block; content: «>»; }

12345 /* display sub-menu indicator */nav li:has(ol, ul) a::after {  display: inlne-block;  content: «>»;}

Возможно, вы хотели бы добавить стили для дебага. Например, подсветить все элементы figure без вложенного img:

CSS /* where’s my image?! */ figure:not(:has(img)) { border: 3px solid red; }

1234 /* where’s my image?! */figure:not(:has(img)) {  border: 3px solid red;}

Как работают CSS псевдоклассы :is, :where и :has

Пока вы не открыли свой CSS в редакторе, добавлю, что :has() это новый псевдокласс и его поддержка хуже чем :is() и :where(). Он работает в Safari 15.4+ и Chrome 101+ под экспериментальным флагом. В 2023 ожидается широкая поддержка.

Заключение

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

:has() – более революционный селектор. Поиск родительского элемента быстро станет популярен, и мы забудем о темных временах! Мы опубликуем полный урок по :has(), когда он станет доступен во всех современных браузерах.

Автор: Craig Buckler

Источник: webformyself.com

Оставьте ответ

Ваш электронный адрес не будет опубликован.