Каскад и специфичность
Каскад — алгоритм, по которому браузер определяет, какое из конфликтующих CSS-правил применить к элементу. Специфичность — числовая «сила» селектора в этом алгоритме.
Зачем нужно
Когда несколько CSS-правил нацелены на один элемент и задают одно и то же свойство, браузеру нужно выбрать победителя. Понимание каскада и специфичности — ключ к предсказуемому поведению стилей и отсутствию «магических» багов.
Где используется
- Разрешение конфликтов между стилями библиотеки и проекта
- Переопределение стилей компонентов
- Отладка «почему стиль не применяется?»
- Архитектура CSS (BEM, ITCSS, CSS Layers)
Предпосылки
- Селекторы — типы селекторов
- Подключение стилей — порядок подключения файлов
Алгоритм каскада
Браузер применяет стили в таком порядке приоритета (от низшего к высшему):
1. Источник стилей (Origin)
| Приоритет | Источник |
|---|---|
| 1 (низший) | User-Agent (стили браузера по умолчанию) |
| 2 | Пользовательские стили (настройки браузера) |
| 3 | Авторские стили (наш CSS) |
| 4 | Авторские !important |
| 5 | Пользовательские !important |
| 6 (высший) | User-Agent !important |
2. Специфичность (Specificity)
При одинаковом источнике сравнивается «вес» селекторов.
3. Порядок появления (Order)
При одинаковой специфичности побеждает правило, объявленное последним.
Расчёт специфичности
Специфичность записывается как три числа: A-B-C
| Компонент | Что считается | Вес |
|---|---|---|
| A | ID-селекторы (#id) |
Высший |
| B | Классы (.class), атрибуты ([attr]), псевдоклассы (:hover) |
Средний |
| C | Теги (div, p), псевдоэлементы (::before) |
Низший |
Примеры расчёта
/* 0-0-1 — один тег */
p { color: black; }
/* 0-1-0 — один класс */
.text() { color: blue; }
/* 0-1-1 — один класс + один тег */
p.text() { color: green; }
/* 1-0-0 — один ID */
#main { color: red; }
/* 1-1-1 — один ID + один класс + один тег */
#main .text() p { color: purple; }
/* 0-2-0 — два класса */
.card .title { color: orange; }
/* 0-2-1 — два класса + один тег */
div.card .title { color: pink; }
/* 1-1-3 — один ID + один класс + три тега */
#content ul li a.link { color: teal; }
Сравнение: кто побеждает?
/* 0-1-0 */
.title { color: blue; }
/* 0-0-2 */
article h1 { color: red; }
/* Побеждает .title (0-1-0 > 0-0-2) */
/* Один класс всегда > любое количество тегов */
/* 0-2-0 */
.card .title { color: blue; }
/* 0-1-1 */
h1.title { color: red; }
/* Побеждает .card .title (0-2-0 > 0-1-1) */
Особые случаи
!important — ядерная кнопка
p {
color: red !important;
}
/* Даже это не перебьёт: */
#main .content p.text() {
color: blue; /* Проиграет !important выше */
}
/* Перебьёт только другой !important с >= специфичностью */
.text() {
color: green !important; /* Проиграет, если специфичность ниже */
}
Правила !important:
- Избегайте в авторских стилях
- Допустимо в utility-классах:
.hidden { display: none !important; } - Допустимо для переопределения inline-стилей сторонних библиотек
inline-стили
<!-- inline-стиль бьёт любую специфичность из CSS-файла -->
<p style="color: red;">Текст</p>
/* Не сработает — inline выше */
#main .content p.text() {
color: blue;
}
/* Сработает — !important перебивает inline */
p {
color: green !important;
}
:where — нулевая специфичность
/* 0-0-0 — специфичность :where всегда 0 */
:where(.card .title) {
color: blue;
}
/* 0-0-1 — перебьёт :where выше */
h2 {
color: red;
}
:is и :not — берут специфичность аргумента
/* Специфичность = наивысший аргумент */
/* :is(#header, .nav) = 1-0-0 (по #header) */
:is(#header, .nav) a {
color: white;
}
/* :not(.active) = 0-1-0 */
li:not(.active) {
opacity: 0.7;
}
Порядок появления (Order of Appearance)
При одинаковой специфичности побеждает последнее правило:
.title { color: blue; }
.title { color: red; } /* Победит — объявлено позже */
Порядок подключения файлов тоже важен:
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="theme.css"> <!-- стили отсюда перебьют base.css -->
Визуализация каскада
Стиль применяется?
│
├─ Есть !important?
│ ├─ Да → сравни специфичность среди !important
│ └─ Нет ↓
│
├─ Сравни источник (user-agent < user < author)
│ └─ Одинаковый ↓
│
├─ Сравни CSS Layer (ранние слои < поздние слои)
│ └─ Одинаковый ↓
│
├─ Сравни специфичность (A-B-C)
│ └─ Одинаковая ↓
│
└─ Последнее правило побеждает
Частые ошибки
- Война
!important— добавление!importantпорождает ещё больше!important:/* Так НЕ надо */ .title { color: red !important; } .card .title { color: blue !important; } #main .card .title { color: green !important; } - Недооценка ID — один
#idперебивает любое количество классов:/* 1-0-0 побеждает */ #title { color: red; } /* 0-10-0 проигрывает */ .a.b.c.d.e.f.g.h.i.j { color: blue; } - Неучтённый порядок файлов — подключение библиотеки после своих стилей перезапишет их
- Забыли про inline-стили — JavaScript часто добавляет
style="", который бьёт CSS-файл
Практика
- Написать 3 правила для одного элемента с разной специфичностью и предсказать результат
- Рассчитать специфичность для 5 сложных селекторов
- Попробовать перебить inline-стиль через
!important - Использовать
:whereдля стилей с нулевой специфичностью - Проверить каскад через DevTools: зачёркнутые стили = проигравшие
Связанные темы
- Селекторы — типы селекторов и их специфичность
- Наследование — другой механизм получения стилей
- CSS Layers (@layer) — современный способ управлять приоритетами
- Подключение стилей — порядок подключения влияет на каскад