:is() та :where() — стріляємо собі в ногу CSS-специфічністю
Для початку пропоную, не підглядаючи у ґуґл, ChatGPT чи куди ви там зараз підглядаєте, дати впевнену відповідь на запитання «Якого псевдоселектора не існує в CSS?»:
...3 ...2 ...1
Час вийшов.
Більш ніж впевнений, що більшість із вас таки обрали правильно-неправильний варіант. Дійсно, псевдоселектора :while()
в CSS (поки) не існує. Але я не менш впевнений і в тому, що згідно з уже зібраною в телеграмі та лінкедині статистикою, варіант з :where()
посів впевнене друге місце. І тут мушу зазначити, що так, цей псевдоселектор справді існує, мало того — робить це доволі давно. Вперше на caniuse він зазеленів у Firefox 78 30 червня далекого й безтурботного 2020 року, а 2 лютого 2021 року його підтримка нарешті з’явилася в Opera 74, завершивши своє втілення у Великій Пʼятірці.
Ба більше, його брат-близнюк :is()
такої недовіри у вас точно не викликав, бо ви десь щось про нього чули й бачили. Тож давайте спробуємо розібратися, в чому вони схожі, а в чому — абсолютно різні.
Почнемо з подібності — обидва псевдоселектори роблять неймовірно дивовижну роботу, а саме дозволяють групувати селектори таким чином, що насправді ці псевдоселектори просто розгортають селектори в доволі складні конструкції:
:is(h1, h2) span {…}
/* “перетворюється” на аналог наступного: */
h1 span, h2 span {…}
/* а ось така конструкція */
:is(h1, h2) :is(span, div) {…}
/* “розгорнеться” в подобу такого правила: */
h1 span,
h1 div,
h2 span,
h2 div {…}
Чому я кажу «в подобу»? Хіба це не те саме? У першому наближенні — так, але є (не) одне «але», яке варто зрозуміти. А саме — :is()
набуває найвищої специфічності з вкладених селекторів. Ага. Тобто, якщо я напишу ось такий селектор: :is(#id, article)
, то специфічність самого :is()
буде такою ж, як і у #id
. Пояснюю на пальцях:
:is(#id, article) .title {…} .title {…} article .title {…}
У цьому прикладі .title всередині article завжди матиме стилі з першого правила, і їх не переб’є навіть ось це правило: article .title {…}
(!important
переб’є, але як його ще хто використовуєте, то це їм пальці перебить треба).
І все через те, що ми застосували стилі до .title
в article через :is()
разом із #id
, то специфічність першого умовного article .title
буде вищою, ніж у того, що описаний в кінці прикладу. Холєра, тут уже я сам плутатися починаю, хто на кому стояв, але їдем далі. Коротше, перший article .title
більш потужний, ніж другий, бо в нього в друзяках ходить селектор по #id
, який, як відомо, найпотужніший.
Можна з певною натяжкою стверджувати, що :is можна замінити простим вкладеним синтаксисом, на кшталт:
:is(section, article) :is(h1, h2) :is(span, div) {…} section, article { h1, h2 { span, div {…} } }
І певною мірою це твердження буде правдивим. Але тільки у тому випадку, коли в межах одного :is()
будуть селектори однієї ваги. Щойно у вас закрадеться туди щось важче бодай на одиничку — ця тотожність вмить щезне, і уся конструкція буде поводитись по іншому.
А, до речі, зворотнє твердження теж правдиве, і вкладений синтаксис «схлопується» в :is()
конструкцію:
section, #hero { button { color: lime; } } :is(section, #hero) button { color: red; }
Ну, або хоча б поводиться за подібними правилами. Подивитися, як цей приклад працює вживу, та побавитися із ним можна ось за цим посиланням: JSFiddle: Nested CSS specificity
Так що, цейво, вважайте: :is()
— це не просто прикольний спосіб писати кучеряві селектори, а ще й інструмент для несподіваного стріляння в ногу непередбачуваною специфічністю. Тож дуже важливо памʼятати про цю його особливість, інакше безсонних ночей з матюкодебагінгом вам не уникнути.
Якщо ж говорити про :where()
, то синтаксично він поводиться рівно так само, як і :is()
, дозволяючи групувати доволі складні селектори, однак на специфічність впливає він рівно навпаки. Тобто всі селектори всередині нього мають нульову специфічність. Ага. Навіть айдішка.
Ось вам приклад:
:where(#id, article) .title {...}.title {...}
Говорячи про специфічність, бравзер повністю проігнорує все, що в дужках, і :where(#id, article) .title
переб’ється звичайнісіньким .title,
якщо той буде десь пізніше в коді.
А якщо нам вистачить мізків написати щось подібне:
:where(#id .title) {...}
то такий селектор переб’ється навіть слабеньким універсальним селектором *
. Отакі пироги.
Тож давайте спробуємо закріпити пройдений матеріал на ось цому прикладі:
:is(body, main) :is(#hero, section) {…} :where(body, main) :where(#hero, section) {…}
Синтаксично цей селектор розгортається в наступну конструкцію:
body #hero, body section, main #hero, main section {…}
але специфічність в цьому випадку буде визначатися уже не «розгорнутою версією», а правилами конкретного псевдоселектора. Тобто :is(body, main) :is(#hero, section) {…}
не відповідатиме селектору body section {…}
саме за специфічністю. Так, стилі будуть застосовуватися за «розгорнутим» селектором, але специфічність буде рахуватися з урахуванням :is()
. Ну а для :where
усе максимально просто — специфічність будь-якої «розгорнутої» конструкції буде нуль.
Тобто у випадку :is()
та :where()
треба розуміти, що вони змінюють правила розрахунку специфічності, за якими застосовуватимуться стилі, і ці правила не відповідають тим, до яких ми звикли.
Якщо спробувати це все пояснити коротко, то :is()
надає всім селекторам усередині себе силу богів, ну або хоча б найсильнішого селектора, а от :where()
навпаки — змусить навіть наймогутніший селектор сидіти тихенько в куточку і не відсвічувати.
Всім дяка за увагу, цьом у лобіка і вчіть CSS.
P.S: Подякувати за цей текст ви можете, долучившись до невеличкої благодійної ініціативи Незамовлений Гепі Міл. Хто долучиться — додатковий цьом у лобіка.
20 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів