:is() та :where() — стріляємо собі в ногу CSS-специфічністю

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Для початку пропоную, не підглядаючи у ґуґл, 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: Подякувати за цей текст ви можете, долучившись до невеличкої благодійної ініціативи Незамовлений Гепі Міл. Хто долучиться — додатковий цьом у лобіка.

👍ПодобаєтьсяСподобалось17
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Трошки оффтопу, але: порекомендуйте на що підписатися/подивитися/почитати фулл-стеку, який останні роки три сидить на бекенді, але не хоче повністю втрачати розуміння сучасного CSS.

Ну, типу, я можу наверстати досить круті штуки, шарю за гріди, флексбокси, псевдоселектори ітд, але коли читаю статті типу цього — маю відчуття що відстаю роки на 3-4, і для досвідченого фронтендера моє верстання виглядатиме як лайно. А хочеться все ж залишатися full-stack

:while() в CSS (поки) не існує

цікаво було б прочитати reasoning чому додано слово _поки_

бо ні в чому не можна бути впевненими. Особливо зважаючи, що зараз іде дуже активна робота над нативними фукнціями і міксинами в CSS.

подумав був мож тому що десь зустрічали що _явні_ цикли збираються в стд css додавати

Циклі ні, а от використання умовно nth індексу для композиції значень — уже на обрії

Про найвищу специфічність is: не знав.
Це як про пріоритетність !mportant з використанням @layers. Свого часу буд дуже здивований )))
Люблю такі статі про CSS, дякую автору.

Дякую. Я теж про приколи зі специфічністю дізнався уже в процесі написання чорнетки тексту. А зараз стикаюся з деякими нюансами @layer, готую великий матеріал, там це буде.

О, це норм заруба буде )))

Не до кінця зрозуміла — :is надає силу найсильнішого селектора в наборі в дужках?

саме так. Тут в ході одного цікавого досліду виявилося, що зі специфічністю бавляться не тільки ці псевдоселектори, то ж ще маю що на цю тему розповісти.

Пролунав постріл в ногу коли обрав :was() в опитуванні! Добре що не влучів. Всі ноги на місці)

Дякую, дуже пізнавально.
Особливо сподобалось що немає :has(), він не вліз?😁

не хочу палити правильні варіанти на наступні опитування, ну!

Дякую, це хороша стаття.

Підписатись на коментарі