От автора: CSS селекторы позволяют выбирать элементы по типу, атрибутам и положению внутри HTML-документа. В этом уроке разберем три новые опции — :is(), :where() и :has().
Селекторы в стилях применяются повсеместно. Пример ниже находит все параграфы и меняет жирность шрифта на bold:
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> текст будет серый:
/* default black */ p { color: #000; } /* gray in <article>, <section>, or <aside> */ article p, section p, aside p { color: #444; }
Это простой пример, однако более сложные страницы усложнят и селекторы. Синтаксическая ошибка в одном селекторе поломает стили для всех элементов. CSS препроцессоры типа Sass разрешают использовать вложенность (что также появится в нативном CSS):
article, section, aside { p { color: #444; } }
Этот CSS идентичен коду выше. Однако такой код писать легче, и он снижает вероятность ошибок. Но есть нюансы:
Пока эта функция не появилась в нативном CSS, придется использовать CSS билд инструменты. Можно использовать Sass, но это может усложнить работу в некоторых командах разработки.
Вложенность может привести к другим проблемам. Очень легко скатиться в глубокую вложенность селекторов, которую неудобно читать, а после преобразования в обычные стили такой код превращается в очень длинные строки.
:is() дает нам нативное CSS решение с поддержкой во всех современных браузерах (без IE):
:is(article, section, aside) p { color: #444; }
Один селектор может содержать любое количество псевдоклассов :is(). Например, сложный селектор ниже применяет зеленый текст ко всем <h1>, <h2> и <p> элементам, которые расположены внутри <section> с классом .primary или .secondary. А также эти элементы должны быть не первым дочерним элементом <article>:
article section:not(:first-child):is(.primary, .secondary) :is(h1, h2, p) { color: green; }
Аналогичный код без :is() требует 6 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; }
Обратите внимание, :is() не может использоваться с псевдоэлементами ::before и ::after. Код ниже упадет с ошибкой:
/* NOT VALID - selector will not work */ div:is(::before, ::after) { display: block; content: ''; width: 1em; height: 1em; color: blue; }
CSS псевдокласс :where
Синтаксис :where() аналогичен :is() и также поддерживается во всех современных браузерах (без IE). Очень часто результат его работы будет таким же, как у предыдущего псевдокласса. Например:
:where(article, section, aside) p { color: #444; }
Разница в специфичности. Специфичность – алгоритм, определяющий приоритет перезаписи CSS селекторов. В примере ниже «article p» более специфичен чем просто «p». Поэтому все параграфы внутри article будут серыми:
article p { color: #444; } p { color: #000; }
Для :is() специфичность равна самому специфичному селектору внутри. Для :where() специфичность равно нулю. Разберем CSS код ниже:
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>
Текст будет красным, как показано в демо ниже:
:is() имеет одинаковую специфичность как и article p, но в коде псевдокласс расположен ниже, поэтому текст будет красным. Чтобы применить синий цвет, нужно удалить article p и :is(), ведь :where() наименее специфичный.
Большая часть людей будет использовать :is(). Однако нулевая специфичность :where() может помочь при сбросе стилей. Тем самым при отсутствии других стилей будут применяться базовые.
Пример CSS сброса ниже применяет top margin в 1em к заголовкам h2 за исключением ситуаций, когда заголовки являются первым дочерними элементами от article:
/* CSS reset */ h2 { margin-block-start: 1em; } article :first-child { margin-block-start: 0; }
Если ниже попробовать применить свое значение для top margin у h2, у нас не получится, так как article :first-child имеет специфичность выше:
/* never applied - CSS reset has higher specificity */ h2 { margin-block-start: 2em; }
Это можно исправить с помощью более специфичного селектора, но это раздувает код и не совсем очевидно для других разработчиков. Вы просто забудете, зачем добавляли такой стиль:
/* styles now applied */ article h2:first-child { margin-block-start: 2em; }
Также проблему решит !important для всех необходимых стилей, но не делайте так! Дальнейшее написание стилей и разработка станут только сложнее:
/* works but avoid this option! */ h2 { margin-block-start: 2em !important; }
Лучше воспользоваться нулевой специфичностью :where() при сбросе:
/* reset */ :where(h2) { margin-block-start: 1em; } :where(article :first-child) { margin-block-start: 0; }
Теперь можно переписывать любые стили из сброса, не обращая внимания на их специфичность. Больше не нужно писать !important:
/* now works! */ h2 { margin-block-start: 2em; }
CSS псевдокласс :has
Синтаксис :has() похож на :is() и :where(), но этот псевдокласс находит элемент, который содержит набор других элементов. Например, ниже стили, применяющие синюю рамку в 2 пикселя ко всем <a>, внутри которых есть один или более <img> или <section>:
/* style the <a> element */ a:has(img, section) { border: 2px solid blue; }
Это самая классная доработка CSS за десятилетия! Разработчики, наконец, могут находить родительские элементы!
Неуловимый «родительский селектор» был одной из самых желанных фич в CSS, но он поставил перед вендорами браузеров вопрос производительности. Поэтому он долго откладывался. Проще говоря:
Браузеры применяют CSS стили к элементу, когда он отрисован на странице. При добавлении дочерних элементов весь родительский блок должен быть перерисован.
Добавление, удаление или изменение элементов в JS затрагивает стили всей страницы вплоть до закрывающего body.
Предполагается, что вендоры решили проблемы производительности, и :has() позволяет делать то, для чего раньше нужно было использовать JS. Например, можно указать стили внешнего тега формы fieldset и кнопки отправки, когда любое из обязательных полей заполнено неправильно:
/* 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; }
Этот пример добавляет индикатор в подменю ссылок, которое содержит список дочерних элементов меню:
/* display sub-menu indicator */ nav li:has(ol, ul) a::after { display: inlne-block; content: ">"; }
Возможно, вы хотели бы добавить стили для дебага. Например, подсветить все элементы figure без вложенного img:
/* where's my image?! */ figure:not(:has(img)) { border: 3px solid red; }
Пока вы не открыли свой CSS в редакторе, добавлю, что :has() это новый псевдокласс и его поддержка хуже чем :is() и :where(). Он работает в Safari 15.4+ и Chrome 101+ под экспериментальным флагом. В 2023 ожидается широкая поддержка.
Заключение
Псевдоклассы :is() и :where() урощают синтаксис CSS. Они позволяют избавиться от лишней вложенности и использования CSS препроцессоров (хотя эти инструменты дают свои преимущества такие как части, циклы и минификация).
:has() – более революционный селектор. Поиск родительского элемента быстро станет популярен, и мы забудем о темных временах! Мы опубликуем полный урок по :has(), когда он станет доступен во всех современных браузерах.
Автор: Craig Buckler
Источник: www.sitepoint.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен