Включите исполнение JavaScript в браузере, чтобы запустить приложение.
20 ноя 2024

Псевдокласс CSS :has()

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

Что такое псевдокласс CSS :has() и как он работает

Псевдокласс :has() в CSS — это относительно новый инструмент, который используется для стилизации элементов на основе их содержимого (потомков): дочерних или вложенных элементов.

Основной синтаксис псевдокласса :has()

Синтаксис псевдокласса выглядит следующим образом:

element:has(selector) {

  /* CSS-стили */

}
css

Заданные стили будут применены к элементу только в том случае, если он содержит дочерние элементы с указанным селектором.

Рассмотрим более конкретный пример:

div:has(img) {

  border: 2px solid blue;

}
css

Этот CSS-код задает стили границ только для тех элементов <div>, которые содержат внутри себя изображения.

:has() может использоваться с различными комбинаторами:

  • > — выбирает прямых потомков:
div:has(> img) {

  border: 2px solid blue;

}
css

Стили границ будут изменены, если <div> имеет дочерний элемент с тегом <img>.

  • + — выбирает следующий элемент:
h2:has(+ p) {

  color: red;

}
css

Цвет заголовка будет изменен на красный, если сразу после него идет тег <p>.

  • ~ — выбирает все родственные элементы:
div:has(~ img) {

  background-color: yellow;

}
css

Цвет фона <div> изменится, если после него следует элемент с тегом <img> (на том же уровне вложенности, но не обязательно сразу после <div>).

  • пробел — выбирает всех потомков элемента вне зависимости от того, прямые они или вложенные:
article:has(figure img) {

  border: 1px solid #ccc;

}
css

Стили границ будут изменены для элементов с тегом <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;

}
bash

можно изменить на следующую:

:is(section, article, aside, nav) h1 {

  font-size: 25px;

}
css

Размер шрифта будет изменен для всех заголовков первого уровня, которые находятся внутри селекторов, указанных в скобках.

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

Псевдокласс :has() же кардинально отличается от :is() и :where(), так как предназначен для того, чтобы выбирать элементы, основываясь на их содержимом или на их дочерних элементах.

Преимущества перед другими селекторами

Основное преимущество :has() перед другими селекторами заключается в его уникальном назначении: в CSS больше нет инструментов, позволяющих легко изменять стили родительских элементов в зависимости от их содержимого. Для таких задач обычно используется JavaScript.

Использование псевдокласса :has() для работы с родительскими элементами

Основное назначение псевдокласса — стилизация родительских элементов с учетом имеющихся у них потомков. 

Например, на сайте есть две статьи, одну из которых нужно выделить:

    
      

Какой-то заголовок

      

Здесь нет класса .highlighted

    
    
      

Какой-то заголовок

      

Здесь есть класс .highlighted — этот блок надо выделить

    
css

Тогда можно применить такой 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;

}
css

Так будут выделены все элементы <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>
css

То можно управлять стилями границы формы на основе корректности или некорректности введенных пользователем данным:

form:has(input:valid) {

  border: 2px solid green;

}

form:has(input:invalid) {

  border: 2px solid red;

}
css

Если в форме есть хотя бы одно некорректно заполненное поле, то граница станет красной, а если нет — зеленой. Стоит отметить, что здесь стили задаются именно для границы формы, а не для отдельных полей внутри нее.

Как :has() помогает в работе с состоянием элементов

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

Сокращенный HTML:

<form class="form">

    <button class="button" type="submit" disabled>Отправить</button>

    <label for="agree">

      <input type="checkbox" id="agree">

      Я согласен (согласна) с политикой конфиденциальности

    </label>

</form>
html

Тогда по умолчанию кнопку можно сделать серой, а при отмеченном чекбоксе — выделить зеленым цветом:

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

}
html

Особенности работы :has() в современных браузерах

Поддержка псевдокласса :has() разными браузерами

Изначально псевдокласс не поддерживался браузерами, но с декабря 2023 года он совместим с последними версиями браузеров и устройств. Несмотря на это, потенциальные проблемы с совместимостью нужно учитывать до сих пор — лучше предусматривать стилизацию на случай, если браузер/устройство пользователя не поддерживает :has(). 

Актуальную информацию о совместимости можно посмотреть здесь.

Примеры применения :has() для улучшения UX и UI

Псевдокласс :has() позволяет улучшить пользовательский опыт (UX) и интерфейс (UI). Рассмотрим несколько примеров.

  • Возвращаясь к способу стилизации границ формы в зависимости от корректности введенных данных, приведенному в разделе об изменении стилей на основе содержимого, можно привести схожий вариант реализации этого правила:
form:has(input[type="text"]:not(:invalid)) {

  border: 2px solid green;

}
css

Если все поля формы заполнены корректно, то граница станет зеленой.

  • При помощи :has() можно выделять блоки, чтобы пользователь обратил на них внимание. Например, в интернет-магазинах товары обычно представляют в виде карточек:
    

Наименование продукта

    

Описание

    Сегодня скидка 20%
    

Наименование продукта

    

Описание

css

Так можно выделить карточки товаров, которые сейчас продаются со скидкой:

.product:has(.discount) {

  border: 2px solid red;

  background-color: #ffefef;

}
css
  • Можно создавать и более интересные эффекты, например, если существует несколько блоков:
    
Блок 1
    
Блок 2
    
Блок 3
    
Блок 4
    
Блок 5
css

То можно изменить стили при наведении курсора на один из них:

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

}
css

При наведении курсора на блок, он сам и текст внутри него будут увеличиваться на 20%, соседние блоки — на 10%, а те, что идут через один — на 5%.

Комбинирование псевдокласса :has() с другими CSS-селекторами

Комбинирование любых селекторов дает возможность создать более сложные правила. :has() не является исключением: этот псевдокласс тоже можно комбинировать с селекторами и другими псевдоклассами, тем самым усложняя условия для стилизации. Рассмотрим несколько примеров.

  • Так можно изменить стили для <div>, если внутри него есть <input>, который находится в фокусе, например, если пользователь кликнул по нему мышью:
div:has(input:focus) {

  border: 2px solid blue;

}
css

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

div.post:has(> img.preview) {

  border: 2px solid green;

}
css

Стили границ для <div> с классом .post изменятся, если они имеют прямого потомка <img> с классом .preview, как в этом HTML-коде:

    Какой-то текст     

Какой-то текст

html

По схожему принципу возможна комбинация с селекторами по атрибуту:

form:has(input[type="submit"]) {

  background-color: green;

}
css

Фон всех <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>
html
  • Также можно создавать более сложные конструкции с использованием нескольких комбинаторов:
div:has(> p + span.important) {

  border: 2px solid red;

}
css

Такой код задает стили границ для всех <div>, которые имеют дочерний элемент <p>, сразу после которого идет <span> с классом .important:

    

Какой-то текст

    Важная информация
html

Ошибки и подводные камни при работе с псевдоклассом :has

  1. Псевдокласс :has() — это полезный инструмент, но его чрезмерное или ненужное в данном случае использование может только усложнить и запутать CSS-код. Поэтому важно знать, когда его следует применять, а также всегда оценивать ситуацию — возможно, есть более оптимальные варианты.
  2. Сейчас этот псевдокласс совместим с последними версиями браузеров и устройств, но проблемы с совместимостью все еще могут возникать.
  3. :has() может часто использоваться с комбинаторами, поэтому важно понять принцип работы каждого из них, а также проверять, нужные ли элементы были выбраны.
  4. Аргументами псевдокласса не могут быть псевдоэлементы, также их нельзя использовать в качестве целевых селекторов.

Недопустимы такие записи:

div:has(::before) {

  border: 2px solid red;

}

::before:has(span) {

  color: blue;

}
css

Также один :has() не может быть вложен в другой.