Итак, :has()
— это CSS-псевдокласс родительского селектора. Другими словами, с помощью :has()
можно изменить родительский элемент, содержащий определённый дочерний элемент либо элемент, следующий за ним.
А вот определение из документации:
CSS псевдокласс
:has()
отображает элемент в том случае, если любой из селекторов, переданный в качестве параметра (относительно:scope
), соответствует хотя бы одному элементу.
Всё ещё не очень понятно, правда? Давайте обратимся к практическим примерам.
Как использовать CSS-селектор :has()?
Рассмотрим следующий HTML-код с двумя родственными элементами с классом everybody
. Как бы вы выбрали тот, у которого есть потомок с классом a-good-time
?
<div class="everybody">
<div>
<div class="a-good-time"></div>
</div>
</div>
<div class="everybody"></div>
С CSS-селектором :has()
это можно реализовать следующим образом:
.everybody:has(.a-good-time) {
animation: party 21600s forwards;
}
Это выбирает первый экземпляр .everybody
и применяет к нему animation
. В этом примере целью является элемент с классом everybody
. Условием является наличие потомка с классом a-good-time
.
<target>:has(<condition>) { <styles> }
Но :has()
гораздо больше возможностей. Вот некоторые из них.
Выбрать anchor
, которые не имеют прямого потомка SVG:
a:not(:has(> svg)) { ... }
Выбрать label
, у которых есть родственный input
:
label:has(+ input) { … }
Выбрать documentElement
, в котором некое состояние присутствует в DOM:
:root:has(.menu-toggle[aria-pressed=”true”]) { … }
И так далее.
Совместимость :has() с браузерами
Не стоит забывать и о совместимости. Поскольку данный псевдокласс является нововведением, его, к сожалению, поддерживают не все браузеры:
CSS-селектор :has() на практике
Это реализация без единой строчки на JavaScript. Для начала можете посмотреть и самостоятельно протестировать пример, который реализован в CodePen:
Теперь рассмотрим его подробнее. Итак, CSS-псевдокласс :hover
срабатывает, когда пользователь наводит на элемент мышью, но при этом активировать его необязательно.
Красивая плавность в примере заключается в создании набора кастомных свойств на основе сглаживающей кривой (easing curve). В нашем случае это:
:root {
--lerp-0: 1;
--lerp-1: 0.5625;
--lerp-2: 0.25;
--lerp-3: 0.0625;
--lerp-4: 0;
}
Затем, чтобы применить это, нам нужны правила, которые обновляют пользовательское свойство --lerp
для :hover
или :focus
для каждого элемента или блока. Код ниже предназначен для выбора пяти блоков с комбинацией родственных комбинаторов (+) и :has()
.
:is(.block:hover, .block:focus-visible) {
--lerp: var(--lerp-0);
z-index: 5;
}
.block:has( + :is(.block:hover, .block:focus-visible)),
:is(.block:hover, .block:focus-visible) + .block {
--lerp: var(--lerp-1);
z-index: 4;
}
.block:has( + .block + :is(.block:hover, .block:focus-visible)),
:is(.block:hover, .block:focus-visible) + .block + .block {
--lerp: var(--lerp-2);
z-index: 3;
}
.block:has( + .block + .block + :is(.block:hover, .block:focus-visible)),
:is(.block:hover, .block:focus-visible) + .block + .block + .block {
--lerp: var(--lerp-3);
z-index: 2;
}
.block:has( + .block + .block + .block + :is(.block:hover, .block:focus-visible)),
:is(.block:hover, .block:focus-visible) + .block + .block + .block + .block {
--lerp: var(--lerp-4);
z-index: 1;
}
Последнее, что нужно сделать, это применить всё к самим блокам. Поскольку блоки выложены с помощью flexbox, можно использовать значение --lerp
, чтобы изменить flex
каждого блока, и translation
для каждого элемента:
.blocks {
display: flex;
}
.block {
transition: flex 0.2s;
flex: calc(0.2 + (var(--lerp, 0) * 1.5));
}
.block__item {
transition: transform 0.2s;
transform: translateY(calc(var(--lerp) * -75%));
}
А вот сами значения --lerp
сгенерированы с помощью утилиты GSAP для распределения значений с помощью сглаживающей кривой.