1. Продвинутые селекторы и специфичность
Продвинутые селекторы и специфичность
Почему два правила CSS, написанных для одного элемента, ведут себя так, будто одно из них не существует? Ответ — в специфичности (specificity), и именно её непонимание становится причиной половины багов в стилях на реальных проектах. На собеседовании этот вопрос задают, чтобы проверить: вы пишете CSS осознанно или методом «добавлю !important и заработает».
Как работает специфичность
Каждый селектор в CSS получает «вес», записанный как кортеж из четырёх чисел: (inline, id, class, element). Браузер сравнивает эти кортежи слева направо — лексикографически, как словарь.
| Селектор | Вес | Пример |
|---|---|---|
| Инлайн-стиль | (1, 0, 0, 0) | style="color: red" |
| ID-селектор | (0, 1, 0, 0) | #header |
| Класс / псевдокласс / атрибут | (0, 0, 1, 0) | .nav, :hover, [type="text"] |
| Элемент / псевдоэлемент | (0, 0, 0, 1) | div, ::before |
Универсальный селектор *, комбинаторы (>, +, ~, пробел) и псевдокласс :where() — не добавляют веса. А вот :is() и :not() принимают вес самого тяжёлого аргумента внутри себя.
> Правило с бо́льшим весом побеждает. При равенстве весов побеждает то, что объявлено позже в коде — это называется каскадом.
Комбинаторы: точный язык связей
Комбинаторы — это операторы между селекторами, которые задают топологические отношения между элементами в DOM-дереве.
.card p найдёт <p> внутри .card на любой глубине.> — прямой потомок: .card > p найдёт только <p>, являющийся непосредственным ребёнком .card.+ — сосед сразу после: h2 + p — первый <p>, следующий непосредственно за <h2>.~ — общий последующий сосед: h2 ~ p — все <p>, идущие после <h2> в одном родителе.На собеседовании часто спрашивают: «Чем отличается + от ~?» Короткий ответ: + берёт только первый соседний элемент, ~ — все последующие.
Псевдоклассы и псевдоэлементы: два разных мира
Псевдоклассы (:hover, :focus, :nth-child(), :not()) описывают состояние элемента. Они добавляют один блок к весу (0, 0, 1, 0).
Псевдоэлементы (::before, ::after, ::first-line, ::selection) создают виртуальные элементы в DOM. Они добавляют (0, 0, 0, 1).
> На практике: если вам нужно стилизовать часть текста внутри элемента или добавить декоративный элемент без HTML — используйте ::before / ::after. Если нужно отреагировать на действие пользователя — псевдокласс.
Продвинутые селекторы: атрибуты и структурные псевдоклассы
Атрибутные селекторы — мощный инструмент, особенно при работе с динамическими классами или data-* атрибутами.
Структурные псевдоклассы позволяют выбирать элементы по их позиции:
:first-child / :last-child — первый/последний среди siblings.:nth-child(an+b) — по формуле: 2n — каждый чётный, 2n+1 — нечётный, 3n+2 — каждый третий со смещением.:nth-of-type(an+b) — то же, но считает только элементы одного типа.:not(.disabled) — исключает элементы по селектору.Ловушки специфичности на собеседовании
Ловушка 1: Накопление классов. Селектор .a.b.c.d имеет вес (0, 0, 4, 0) — четыре класса. Он перебивает #sidebar с весом (0, 1, 0, 0)? Нет: id всегда тяжелее любого количества классов. Но .a.b.c.d перебьёт .nav.active с весом (0, 0, 2, 0).
Ловушка 2: !important не решает проблему. Когда два правила оба с !important, побеждает то, у которого выше специфичность. А если специфичность одинакова — правило, объявленное позже. Злоупотребление !important создаёт «гонки вооружений», где каждое следующее правило вынуждено тоже использовать !important.
Ловушка 3: Порядок в CSS-файле. При равной специфичности побеждает последнее правило. Это значит, что порядок подключения стилейheets влияет на результат — и это частый вопрос на интервью.
Практический совет: держите специфичность низкой и плоской. Используйте классы, избегайте вложенности более двух уровней. Если вы пишете div.container ul.nav li.item a.link — вы делаете что-то не так. Достаточно .nav-link.
Каскадные слои (@layer)
Начиная с 2022 года, CSS поддерживает каскадные слои — механизм явного управления приоритетом целых блоков стилей, независимо от специфичности селекторов внутри них.
Слои, объявленные позже в списке @layer, имеют более высокий приоритет — даже если внутри них селекторы с минимальной специфичностью. Это решает вечную проблему конфликтов между стилями библиотек и пользовательскими стилями: библиотеку помещают в ранний слой, а свои стили — в поздний.
На собеседовании @layer — это маркер, что кандидат следит за современными стандартами. Но даже без него понимание специфичности на уровне кортежей — базовый навык, без которого невозможно эффективно работать с CSS на серьёзных проектах.