.
Интересный случай с селекторами
Недавно 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. И находит его.
И всё нормально.
В результате обсуждения такое поведение было признано правильным. Пользуйтесь и будьте внимательны.