Основы каскада и специфичности
Почти так же неизбежно, как смерть и налоги, разработчики будут ссылаться на то, что каскад и специфичность — это то, что делает работу с CSS сложной. Конечно, каскад и специфичность будут создавать проблемы, если не учитывать их в CSS, но если есть базовое понимание, я обещаю, что ваши навыки работы с CSS взлетят до небес.
Чаще всего каскад и специфичность объясняются с точностью до сантиметра в обширных руководствах или подробных статьях. Подобные статьи — это замечательно, но думаю, что их размер и сложность могут напугать разработчиков, поэтому собираюсь их упростить. На самом деле, простота — один из самых эффективных способов сохранить любую кодовую базу управляемой и поддерживаемой. Особенно это касается CSS. Давайте разберёмся.
C в CSS означает Cascade (каскад)
Давайте сначала разберёмся со слоном в комнате. Каскад — это алгоритм, разрешающий конфликты, когда к элементу HTML может применяться несколько правил.
Рассмотрим очень простой пример:
.my-element {
background: goldenrod;
background: coral;
}У этого элемента есть два объявления фона. Каскад сделал своё дело и определил, что у .my-element фон будет coral. Но почему?
Поскольку декларация coral идёт после декларации goldenrod, coral побеждает. В подавляющем большинстве случаев при создании стандартных CSS запомните это, и всё будет в порядке. Однако есть и исключения, поэтому давайте остановимся на них, ведь первый пункт был очень простым, верно?
Порядок важности
Нет, это не странная британская королевская семья; это простой набор правил, которые нужно запомнить, потому что не все свойства одинаковы. Я имею в виду, что некоторые типы CSS свойств будут приоритетнее других. Этот список упорядочен от наименее до наиболее специфичного.
Обычные объявления
Это свойства background, padding, line-height и margin. То есть то, что большую часть дня вы пихаете в свой код.
Активные анимации
Это происходит, когда на анимируемые свойства влияет активная анимация @keyframes.
Объявления использующие !important
Не бойтесь, а восхищайтесь: скромное свойство !important делает именно то, что заявлено: определяет важное значение. Оно добавляется как часть значения объявления следующим образом:
.my-element {
background: grey !important;
}В конечном счёте оно побеждает (почти) всегда, но относитесь к нему так, как будто оно побеждает всегда, и с ним не будет проблем.
Свойство !important предназначено для того, чтобы его соблюдали по факту его важности, поэтому используйте его только тогда, когда чувствуете, что это абсолютно необходимо. Я напишу об этом подробнее, если это потребуется, но используйте его с умом, и !important может быть действительно полезным и поможет сохранить вашу кодовую базу простой.
Порядок происхождения
Этот порядок учитывается каскадом от наименее специфичного к наиболее специфичному.
Базовые стили пользовательских агентов
Каждый основной браузер поставляется с таблицей стилей пользовательского агента. Это стили применяемые по умолчанию к HTML элементам, чтобы каждый элемент не выглядел одинаково, если нет CSS, применяемого к данному HTML.
Локальные пользовательские стили
Эти стили в значительной степени относятся к уровню операционной системы, например, к предпочтениям размера шрифта. Локальные пользовательские стили также технически относятся к CSS, создаваемым в расширениях браузера, но в современных условиях рекомендую считать, что они внедряются как <style>, который будет более специфичным, чем стили, создаваемые в файлах CSS.
Авторский CSS
Эй, чемпион, это твой продукт, который ты пишешь!
Авторский !important
Да, как и говорилось ранее, эти значения !important победят стандартные значения свойств.
Локальные пользовательские стили с !important
Допустим, необходимо, чтобы размер шрифта был одинаковым на всех сайтах. Комбинация операционной системы и браузера может технически объявить это свойством !important, и это победит всё вышеперечисленное.
!important пользовательского агента
Наконец, если стили пользовательского агента имеют значение !important, то в конечном итоге победит этот вариант.
На этом с каскадом всё, давайте займёмся его лучшим другом — специфичностью.
Специфичность
Забавный факт: поскольку я родом из Йоркшира в Великобритании, мне трудно произнести это слово, что для человека, преподающего CSS, не является идеальным. Я отвлекаюсь от темы, но не просто так, а чтобы дать передышку — расправьте плечи и выдохните, потому что специфичность — это не сложно, поверьте мне.
Вернёмся к .my-element, но изменим CSS. Важно отметить, что используется элемент <div>, у которого есть атрибут class.
div {
background: goldenrod;
}
.my-element {
background: coral;
}Победителем становится coral, потому что у .my-element оценка специфичности 0-1-0, а у div оценка специфичности — 0-0-1. Давайте разберёмся с оценками подробнее.
Оценка специфичности
Каждое правило селектора получает оценку. Чем выше балл, тем больше вероятность применения CSS этого правила. Оценка ведётся в таком формате: 0-0-0, или "сотни"-"десятки"-"единицы". Он расширяется, но не будем об этом беспокоиться, пока не понадобится. Давайте вкратце рассмотрим, как начисляются оценки.
Универсальный селектор — он же селектор подстановочных знаков
* {
/* Ваши CSS объявления и свойства здесь */
}Оценка: 0-0-0 — 0 баллов
Селектор типа — он же селектор элемента
h1 {
/* Ваши CSS объявления и свойства здесь */
}Оценка: 0-0-1 — 1 балл
Селектор псевдоэлемента
::before {
/* Ваши CSS объявления и свойства здесь */
}Оценка: 0-0-1 — 1 балл
Селектор класса
.my-element {
/* Ваши CSS объявления и свойства здесь */
}Оценка: 0-1-0 — 10 баллов
Селектор псевдокласса
:hover {
/* Ваши CSS объявления и свойства здесь */
}Оценка: 0-1-0 — 10 баллов
Селектор атрибута
[href] {
/* Ваши CSS объявления и свойства здесь */
}Оценка: 0-1-0 — 10 баллов
Селектор идентификатора
#myElement {
/* Ваши CSS объявления и свойства здесь */
}Оценка: 1-0-0 — 100 баллов
Атрибут встроенного/инлайн стиля
<div style="background: blue"></div>Оценка: 1-0-0-0 — 1000 баллов
Правило !important
.my-element {
background: red !important;
}Оценка: 1-0-0-0-0 — 10000 баллов
Погодите-ка, !important — это же для каскада, верно?! Это и каскад, и специфичность. Некто с именем вроде Чада Смита в Twitter может назвать это причиной того, что CSS "отстой". Но это не так. Потому что всё, что нужно сделать, это запомнить, что !important относится к каскаду и специфичности, точно так же, как необходимо помнить, что JavaScript не может правильно сравнивать числа. Во всех языках программирования есть такие вещи, которые нужно изучать, и CSS ничем не отличается.
Помните, я говорил, что счёт будет расширяться? Так вот. Как для атрибутов встроенного стиля, так и для правил !important добавляется дополнительное число. Для вас, дорогой читатель, это сигнал о том, что CSS, который вы пишете, должен быть чертовски важным, потому что вы находитесь в области очень высокой специфичности.
Комбинирование селекторов даёт больше баллов
Теперь вы знаете, сколько очков даёт каждый тип селектора, давайте объединим их, чтобы продемонстрировать, как работает эта часть.
h1#myElement {
/* Ваши CSS объявления и свойства здесь */
}Мы получили 1 балл, потому что используем селектор типа (h1), и 100 баллов, потому что также используем селектор идентификатора (#myElement). Поэтому оценка специфичности здесь составляет 1-0-1 — 101 балл.
Давайте попробуем ещё раз.
h1.my-element::before {
/* Ваши CSS объявления и свойства здесь */
}Мы получили 2 балла, потому что используем селектор типа (h1) и селектор псевдоэлемента ::before. Мы также получаем 10 баллов, потому что используем селектор класса (.my-element). Таким образом, оценка специфичности составляет 0-1-2 — 12 баллов.
:not(), :is() и :where()
Допустим, есть такой CSS:
h1:is(.my-element) {
/* Ваши CSS объявления и свойства здесь */
}Он имеет точно такой же показатель специфичности, как и:
h1.my-element {
/* Ваши CSS объявления и свойства здесь */
}Это связано с тем, что при использовании :is() и :not() будет выбран наиболее специфичный селектор, переданный в эти псевдоклассы, поэтому не стоит использовать их для повышения специфичности.
С :where() этот псевдокласс и любой селектор, переданный в него, не имеют никакой специфичности. Вернёмся к одному из предыдущих селекторов, чтобы продемонстрировать это.
h1#myElement {
/* Ваши CSS объявления и свойства здесь */
}Счёт 1-0-1 — 101 балл. Добавим :where().
h1:where(#myElement) {
/* Ваши CSS объявления и свойства здесь */
}Теперь оценка 0-0-1 — 1 балл, потому что 100 баллов от селектора ID были отброшены, так как он находится внутри псевдокласса :where(). Именно поэтому вы, вероятно, заметили его использование в CSS сбросах, потому что сброшенные стили гораздо легче переопределить благодаря низкой специфичности :where().
"Дочерние" и "родственные" селекторы не добавляют специфичности
Следует помнить, что использование дочерних и родственных селекторов (>, ~ и +) не влияет на оценку специфичности. Например, эти селекторы имеют одинаковые показатели:
.my-element li {
/* Ваши CSS объявления и свойства здесь */
}
.my-element > li {
/* Ваши CSS объявления и свойства здесь */
}Инструменты разработчика — ваш лучший друг
Если вы откроете "Инструменты разработчика" в браузере и перейдёте на вкладку "Стили", это будет выглядеть примерно так:

Панель CSS "Инструменты разработчика" демонстрирует, что в верхней части побеждает .my-element. Правила фона для div отменяются, потому что my-element более специфичен
То, что менее специфично с точки зрения как каскада, так и специфичности, будет находиться в нижней части панели. Всё, что более специфично, будет находиться выше на панели. То, что было отброшено, будет вычеркнуто, а то, что не было вычеркнуто, будет применено. Одно это знание должно облегчить вам жизнь!
Подведение итогов
Настоятельно рекомендую иметь в своём арсенале калькулятор специфичности. Мой любимый — от фантастических ребят из Polypane. Их калькулятор специфичности очень прост в использовании и отлично справляется со своей задачей.
Кроме того, есть новые возможности CSS, которые могут помочь, например, Каскадные слои. Лично мне они пока не пригодились, поэтому не могу о них рассказать. Пусть об этом расскажет тот, кто в этом разбирается.
В заключение скажу, что писать об этом очень скучно, но это очень важно понимать. Надеюсь, статья поможет почувствовать себя более комфортно с двумя наиболее фундаментальными частями CSS. Если ищете быстрый совет, как справиться с этим в реальном мире, я бы посоветовал держать селекторы как можно ниже. Если попали в тупик, помните о порядке происхождения и используйте инструменты разработчика! Помните, что если в панели CSS он перечёркнут, значит, в дело вступило что-то более специфичное.