.
Интересный случай с селекторами
Недавно CSSWG рассматривало ишью о разнице в
поведении селекторов :has(:is())
и просто :has()
.
Обсуждение показалось мне очень интересным и я хочу подсветить и разобрать его.
Про has
и is
Функциональный псевдокласс is()
сопоставляется с любым селектором из переданного ему в качестве
аргумента списка. То есть, например, здесь тексты в обоих блоках будут красного цвета:
<div class="a">A</div>
<div class="b">B</div>
<style>
div:is(.a, .b) { color: red; }
</style>
Функциональный псевдокласс has()
сопоставляется с теми боксами, которые имеют ниже по дереву
указанных в аргументе потомков. То есть, например, в этом случае только первый текст будет красным:
<div class="a">A <span></span></div>
<div class="b">B</div>
<style>
div:has(span) { color: red; }
</style>
Про is
внутри has
Очевидно, что мы легко может помещать is
внутрь has
. Например, в этому случае
красными будут два нижних текста:
<div class="a">A</div>
<div class="b">B <i></i></div>
<div class="c">C <span></span></div>
<style>
div:has(:is(span, i)) { color: red; }
</style>
Мы даже может без проблем сконструировать такой селектор:
<div class="c">C <span><i></i></span></div>
<style>
div:has(:is(span i)) { color: red; }
</style>
Однако, что будет, если мы напишем первым селектором внутри is
что-то, что выходит за область
видимости селектора перед has
?
Про утечку из области видимости
В этом разделе я буду использовать текст в псевдоэлементах как маркер, потому что color
- свойство наследуемое, а в примерах блоки вложены друг в друга.
Рассмотрим такую конструкцию:
<div id="outer">outer
<div id="middle">middle
<div id="inner">inner</div>
</div>
</div>
<style>
div:has(#outer #inner)::before {
content: "1_"
}
</style>
Ни к одному из имеющихся элементов этот селектор не применится. Потому что has
пытается найти
внутри своей области видимости весь селектор-аргумент сразу. Оно ищет внутри себя #outer
в
который был бы вложен #inner
. И не находит, потому что самый внешний блок, к которому
применяется часть селектора до has
(селектор по тегу div
) - это блок
#outer
. А внутри него нет другого блока #outer
. Это все равно, что написать
#outer:has(#outer #inner)
. Очевидно, что #outer
не находится сам внутри себя.
Попробуем изменить ситуацию:
<div id="outer">outer
<div id="middle">middle
<div id="inner">inner</div>
</div>
</div>
<style>
div:has(:is(#outer #inner))::before {
content: "1_"
}
</style>
Теперь has
пытается найти внутри себя #inner
, который являлся бы потомком
#outer
. И это работает. Под условие подходят блоки #middle
и #outer
.
Оба имеют внутри себя #inner
, одним из предков которого является #outer
.
Получается, что has
в этом случае как бы смотрит дальше свой области видимости.
Приведу так же более утрированный пример ради лучше иллюстрации:
<div id="outer">outer
<div>div
<div>div
<div>div
<div id="middle">middle
<div id="inner">inner</div>
</div>
</div>
</div>
</div>
</div>
<style>
#middle:has(:is(#outer #inner))::before {
content: "1_"
}
</style>
До has
стоит прямое указание на блок #middle
, поэтому has
начинает
искать вглубь него. Он ищет там блок #inner
, один из предков которого был бы блок
#outher
. И находит его.
И всё нормально.
В результате обсуждения такое поведение было признано правильным. Пользуйтесь и будьте внимательны.