Что такое псевдокласс CSS :has() и как он работает
Псевдокласс :has() в CSS — это относительно новый инструмент, который используется для стилизации элементов на основе их содержимого (потомков): дочерних или вложенных элементов.
Основной синтаксис псевдокласса :has()
Синтаксис псевдокласса выглядит следующим образом:
element:has(selector) {
/* CSS-стили */
}
Заданные стили будут применены к элементу только в том случае, если он содержит дочерние элементы с указанным селектором.
Рассмотрим более конкретный пример:
div:has(img) {
border: 2px solid blue;
}
Этот CSS-код задает стили границ только для тех элементов <div>, которые содержат внутри себя изображения.
:has() может использоваться с различными комбинаторами:
- > — выбирает прямых потомков:
div:has(> img) {
border: 2px solid blue;
}
Стили границ будут изменены, если <div> имеет дочерний элемент с тегом <img>.
- + — выбирает следующий элемент:
h2:has(+ p) {
color: red;
}
Цвет заголовка будет изменен на красный, если сразу после него идет тег <p>.
- ~ — выбирает все родственные элементы:
div:has(~ img) {
background-color: yellow;
}
Цвет фона <div> изменится, если после него следует элемент с тегом <img> (на том же уровне вложенности, но не обязательно сразу после <div>).
- пробел — выбирает всех потомков элемента вне зависимости от того, прямые они или вложенные:
article:has(figure img) {
border: 1px solid #ccc;
}
Стили границ будут изменены для элементов с тегом <article>, которые содержат внутри <figure>, при этом внутри <figure> содержится элемент с тегом <img>.
Особенности и преимущества использования
- Возможность создания более сложных правил стилизации;
- :has() позволяет стилизовать элементы динамически — стили родительских элементов зависят от того, как изменяются их содержимое, например, меняется состояние потомков;
- сокращение использования JS — многие задачи можно решить при помощи CSS;
- улучшение пользовательского опыта — псевдокласс :has() может использоваться, например, для создания интерактивных элементов, которые помогают пользователям лучше ориентироваться на странице и быстрее находить нужную информацию.
Как работает псевдокласс :has() в сравнении с другими селекторами CSS
Отличие от псевдоклассов :is() и :where()
Псевдокласс :is() используется для группировки селекторов — так можно сделать код более компактным и читаемым. В качестве аргументов он принимает список из селекторов, если хоть один из них соответствует элементу, то стили элемента будут изменены. Важная особенность :is() — специфичность всего селектора определяется наиболее специфичным селектором среди аргументов.
С помощью :is() такую запись:
section h1,
article h1,
aside h1,
nav h1 {
font-size: 25px;
}
можно изменить на следующую:
:is(section, article, aside, nav) h1 {
font-size: 25px;
}
Размер шрифта будет изменен для всех заголовков первого уровня, которые находятся внутри селекторов, указанных в скобках.
:where() имеет ту же функцию, что :is(), однако этот псевдокласс всегда имеет специфичность, равную нулю: то, какие селекторы были переданы в качестве аргументов не имеет значения. Поэтому правила, написанные с :where() могут перекрываться другими, даже если они записаны позже.
Псевдокласс :has() же кардинально отличается от :is() и :where(), так как предназначен для того, чтобы выбирать элементы, основываясь на их содержимом или на их дочерних элементах.
Преимущества перед другими селекторами
Основное преимущество :has() перед другими селекторами заключается в его уникальном назначении: в CSS больше нет инструментов, позволяющих легко изменять стили родительских элементов в зависимости от их содержимого. Для таких задач обычно используется JavaScript.
Использование псевдокласса :has() для работы с родительскими элементами
Основное назначение псевдокласса — стилизация родительских элементов с учетом имеющихся у них потомков.
Например, на сайте есть две статьи, одну из которых нужно выделить:
Какой-то заголовок
Здесь нет класса .highlighted
Какой-то заголовок
Здесь есть класс .highlighted — этот блок надо выделить
Тогда можно применить такой CSS-код:
section:has(article.highlighted) {
background-color: #f0f0f0;
padding: 20px;
border: 1px solid #ccc;
}
article {
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
}
.highlighted {
border-color: #ff6347;
background-color: #ffe4e1;
}
Так будут выделены все элементы <article> с классом .highlighted внутри <section>.
Примеры использования :has() для динамического стилизирования
Применение :has() для изменения стилей на основе содержимого
:has() позволяет менять стили элементов в зависимости от их содержимого. Например, если на сайте есть форма с полями «имя» и «электронная почта»:
<form action="/submit" method="post">
<label for="name">Имя:</label>
<input type="text" id="name" name="name" required minlength="3" placeholder="Введите имя">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required placeholder="Введите email">
<input type="submit" value="Отправить">
</form>
То можно управлять стилями границы формы на основе корректности или некорректности введенных пользователем данным:
form:has(input:valid) {
border: 2px solid green;
}
form:has(input:invalid) {
border: 2px solid red;
}
Если в форме есть хотя бы одно некорректно заполненное поле, то граница станет красной, а если нет — зеленой. Стоит отметить, что здесь стили задаются именно для границы формы, а не для отдельных полей внутри нее.
Как :has() помогает в работе с состоянием элементов
Рассмотрим способы использования псевдокласса при работе с состоянием элементов на примере чекбокса. Допустим, на сайте есть форма, которую можно отправить только при согласии пользователя с политикой конфиденциальности.
Сокращенный HTML:
<form class="form">
<button class="button" type="submit" disabled>Отправить</button>
<label for="agree">
<input type="checkbox" id="agree">
Я согласен (согласна) с политикой конфиденциальности
</label>
</form>
Тогда по умолчанию кнопку можно сделать серой, а при отмеченном чекбоксе — выделить зеленым цветом:
.button {
background-color: gray;
color: white;
border: none;
padding: 10px 20px;
cursor: not-allowed;
opacity: 0.5;
}
form:has(input[type="checkbox"]:checked) .button {
background-color: green;
cursor: pointer;
opacity: 1;
}
Особенности работы :has() в современных браузерах
Поддержка псевдокласса :has() разными браузерами
Изначально псевдокласс не поддерживался браузерами, но с декабря 2023 года он совместим с последними версиями браузеров и устройств. Несмотря на это, потенциальные проблемы с совместимостью нужно учитывать до сих пор — лучше предусматривать стилизацию на случай, если браузер/устройство пользователя не поддерживает :has().
Актуальную информацию о совместимости можно посмотреть здесь.
Примеры применения :has() для улучшения UX и UI
Псевдокласс :has() позволяет улучшить пользовательский опыт (UX) и интерфейс (UI). Рассмотрим несколько примеров.
- Возвращаясь к способу стилизации границ формы в зависимости от корректности введенных данных, приведенному в разделе об изменении стилей на основе содержимого, можно привести схожий вариант реализации этого правила:
form:has(input[type="text"]:not(:invalid)) {
border: 2px solid green;
}
Если все поля формы заполнены корректно, то граница станет зеленой.
- При помощи :has() можно выделять блоки, чтобы пользователь обратил на них внимание. Например, в интернет-магазинах товары обычно представляют в виде карточек:
Наименование продукта
Описание
Сегодня скидка 20%
Наименование продукта
Описание
Так можно выделить карточки товаров, которые сейчас продаются со скидкой:
.product:has(.discount) {
border: 2px solid red;
background-color: #ffefef;
}
- Можно создавать и более интересные эффекты, например, если существует несколько блоков:
Блок 1
Блок 2
Блок 3
Блок 4
Блок 5
То можно изменить стили при наведении курсора на один из них:
.card-container {
display: flex;
gap: 10px;
justify-content: center;
padding: 20px;
}
.card {
padding: 20px;
background-color: lightgray;
border-radius: 5px;
transition: transform 0.3s ease, font-size 0.3s ease;
font-size: 1rem;
width: 150px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.card:hover {
transform: scale(1.2);
font-size: 1.6rem;
}
.card:hover + .card {
transform: scale(1.1);
font-size: 1.4rem;
}
.card:hover + .card + .card {
transform: scale(1.05);
font-size: 1.2rem;
}
.card:has(+ .card:hover) {
transform: scale(1.1);
font-size: 1.4rem;
}
.card:has(+ .card + .card:hover) {
transform: scale(1.05);
font-size: 1.2rem;
}
При наведении курсора на блок, он сам и текст внутри него будут увеличиваться на 20%, соседние блоки — на 10%, а те, что идут через один — на 5%.
Комбинирование псевдокласса :has() с другими CSS-селекторами
Комбинирование любых селекторов дает возможность создать более сложные правила. :has() не является исключением: этот псевдокласс тоже можно комбинировать с селекторами и другими псевдоклассами, тем самым усложняя условия для стилизации. Рассмотрим несколько примеров.
- Так можно изменить стили для <div>, если внутри него есть <input>, который находится в фокусе, например, если пользователь кликнул по нему мышью:
div:has(input:focus) {
border: 2px solid blue;
}
В комбинации с :has() можно использовать селекторы по классу:
div.post:has(> img.preview) {
border: 2px solid green;
}
Стили границ для <div> с классом .post изменятся, если они имеют прямого потомка <img> с классом .preview, как в этом HTML-коде:
Какой-то текст
По схожему принципу возможна комбинация с селекторами по атрибуту:
form:has(input[type="submit"]) {
background-color: green;
}
Фон всех <form>, которые содержат <input> с атрибутом "submit" будет изменен:
<form action="/submit" method="post">
<label for="name">Имя:</label>
<input type="text" id="name" name="name">
<label for="email">Электронная почта:</label>
<input type="email" id="email" name="email">
<input type="submit" value="Отправить">
</form>
- Также можно создавать более сложные конструкции с использованием нескольких комбинаторов:
div:has(> p + span.important) {
border: 2px solid red;
}
Такой код задает стили границ для всех <div>, которые имеют дочерний элемент <p>, сразу после которого идет <span> с классом .important:
Какой-то текст
Важная информация
Ошибки и подводные камни при работе с псевдоклассом :has
- Псевдокласс :has() — это полезный инструмент, но его чрезмерное или ненужное в данном случае использование может только усложнить и запутать CSS-код. Поэтому важно знать, когда его следует применять, а также всегда оценивать ситуацию — возможно, есть более оптимальные варианты.
- Сейчас этот псевдокласс совместим с последними версиями браузеров и устройств, но проблемы с совместимостью все еще могут возникать.
- :has() может часто использоваться с комбинаторами, поэтому важно понять принцип работы каждого из них, а также проверять, нужные ли элементы были выбраны.
- Аргументами псевдокласса не могут быть псевдоэлементы, также их нельзя использовать в качестве целевых селекторов.
Недопустимы такие записи:
div:has(::before) {
border: 2px solid red;
}
::before:has(span) {
color: blue;
}
Также один :has() не может быть вложен в другой.