Введение

Как и любая моя статья, эта начинается с введения. Дабы не говорить на разных языках, начнём с самых основ в мире CSS, то есть с терминологии. Чувствую себя преподавателем в университете — как же это круто.

Для того чтобы успешно разбираться в CSS и стать крутым специалистом в этой области, нужно первым делом разобраться с тем, что называют селектором. На изображении ниже представлена простейшая структура CSS-правила.

Простейшая структура CSS-правила

Селектор — это строка, представляющая собой формальное описание структуры, на основе которого выбирается элемент или группа элементов в дереве документа и применяется объявленный блок свойств.

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

Я не буду вдаваться в подробности того, что селекторы должны начинаться с буквы и некоторых других символов. Я также не буду говорить про неинтересные прописные истины, которые можно найти в любой статье на тему основ CSS. Речь в этой статье пойдёт про вес CSS-селекторов, а если говорить простым языком — о его числовом представлении и понимании.

Вес селекторов

Нет, здесь не идёт речь об их реальном весе (его нет), а также о количестве символов. Такой вес нельзя измерить весами, так как он не вещественен в обычном понимании и вообще не существует в нашем с вами мире людей. Зато он есть в виде нулей и единиц. Получается, что селектор можно взвесить, используя некую систему, о которой мы и будем говорить дальше.

Что такое вес селектора?

Вес селектора — это условные четыре позиции x, x, x, x, которые заполняются нулями и единицами в соответствии с содержимым селектора. Каждая из позиций имеет своё содержимое:

  • Инлайн стили
  • Идентификаторы
  • Классы, атрибуты и псевдоклассы
  • Теги и псевдоэлементы

Как это читать?

Очень просто. Справа налево. Слева идут числа старшего разряда, поэтому они имеют больший вес, числа, идущие справа, наоборот, имеют наименьший вес. Всё это станет понятным дальше, поэтому можно даже не вдумываться в смысл этого абзаца.

Как заполнять?

Немного сложнее, чем читать. Рассмотрим предложенную систему четырёх позиций на реальном примере для того, чтобы понять всю технологию.

h1 {
  color: #777;
}

В этом примере селектором выступает заголовок h1, который состоит из одного тега. Получается, что напротив столбца «тег» мы ставим единичку. Получается следующая картина: 0, 0, 0, 1.

Всё это замечательно, но в реальных проектах встречается разве что в ядре стилей или normalize, а это значит, что нужно усложнить задачу.

#main .container article.post > header h1.giga {
  color: #777;
}

Пример получился хардкорнее, чем предыдущий и в реальной жизни достоин премии «самого избыточного селектора». Такая избыточность должна преследоваться по закону, но подробнее на эту тему мы поговорим в отдельной части статьи. Окей, вернёмся к примеру и немного поработаем весами.

Давайте начнём слева, так как в начале стоит единственный идентификатор #main. Далее мы видим три класса .container, .post и .giga, а также три тега article, header и h1. Для ещё большей наглядности я распишу это в виде этапов:

// Селектор
#main .container article.post > header h1.giga

// Начальный вес
0, 0, 0, 0

// Идентификаторы
#main         0, 1, 0, 0

// Классы, атрибуты и псевдоклассы
.container    0, 1, 1, 0
.post         0, 1, 2, 0
.giga         0, 1, 3, 0

// Теги и псевдоэлементы
article       0, 1, 3, 1
header        0, 1, 3, 2
h1            0, 1, 3, 3

// Итог
#main .container article.post > header h1.giga
=>
0, 1, 3, 3

Давайте напишем какой-нибудь безбашенный селектор, который я, надеюсь, никогда не увижу ни у кого в коде:

// Селектор
body.page-posts #main .container article.post ul.list-unstyled > li:first-child h2.article-title:hover {
  color: #333;
}

// Начальный вес
0, 0, 0, 0

// Идентификаторы
#main             0, 1, 0, 0

// Классы, атрибуты и псевдоклассы
.page-posts       0, 1, 1, 0
.container        0, 1, 2, 0
.post             0, 1, 3, 0
.list-unstyled    0, 1, 4, 0
:first-child      0, 1, 5, 0
.article-title    0, 1, 6, 0
:hover            0, 1, 7, 0

//  Теги и псевдоэлементы
body              0, 1, 7, 1
article           0, 1, 7, 2
ul                0, 1, 7, 3
li                0, 1, 7, 4
h2                0, 1, 7, 5

// Итог
body.page-posts #main .container article.post ul.list-unstyled > li:first-child h2.article-title:hover
=>
0, 1, 7, 5

Ну и напоследок, для полного понимания темы, будет пример с атрибутами и псевдоэлементами.

// Селектор
.main[data-columns]:before {
  content: "3 .column.size-1of3";
}

// Начальный вес
0, 0, 0, 0

// Идентификаторы
0, 0, 0, 0

// Классы, атрибуты и псевдоклассы
.main             0, 0, 1, 0
[data-columns]    0, 0, 2, 0

//  Теги и псевдоэлементы
:before           0, 0, 2, 1

// Итог
.main[data-columns]:before
=>
0, 0, 2, 1

Вот такие вот дела.

На самом деле, тема очень простая, но очень важная на практике для посредственного (минимального) понимания того, как браузер определяет какой блок объявлений необходимо применить к тому или иному элементу на странице.

А что, если вес селекторов одинаковый?

Допустим, что у вас есть два или несколько селекторов так или иначе указывающих на один и тот же элемент. И вот так сложилось, что вы посчитали или просто взглянули на них, и вес оказался одинаковым. Не стоит отчаиваться, просто блок объявлений последнего селектора в вашем CSS-коде из этой группы и будет применяться к элементу. Как-то так. Мне кажется это логичным. Прямо как в поговорке «кто не успел, тот опоздал», но наоборот: «кто опоздал, тот и успел».

Зачем это нужно?

Это очень интересный момент, ибо понять какой блок объявлений будет применяться к элементу, можно и без расчёта веса селектора, то есть просто посмотрев на него и слегка подумав. Однако, глазомер может подвести, а вот машина не подведёт, посчитает вес и испортит вёрстку. Ошибка, конечно, не машины, а прослойки между компьютером и стулом, но это уже совсем другая история.

Ещё одним применением такого вот расчёта являются сервисы или скрипты, которые строят диаграммы специфичности CSS. Это может быть очень удобно для анализа и оценки избыточности вашего кода.

Интересным фактом будет то, что единственный раз, когда мне приходилось считать вес селектора, был тестом от Mail.ru на какой-то сертификат. Если интересно, то я поищу этот тест у себя в истории.

Я ленивый и не хочу понимать, как считать вес селекторов, что тогда?

Ох, специально для вас у меня есть сервис, на который я наткнулся при подготовке к изложению этого материала: Specificity Calculator — это простой и эффективный калькулятор веса селекторов.

Специфичность селекторов

Раз уж зашла речь про вес CSS-селекторов, то неизбежно задумываешься о том, как бы его оценить: когда нужно ему худеть, а когда наоборот — поправляться. Как и у человека, у селекторов всё таки есть оптимальный вес.

Так сложилось, что многие веб-разработчики считают оптимальной специфичностью селекторов три вложения. Максимальной специфичностью селекторов называется пять вложений и рекомендуется стараться это число не превышать. Разумеется, вложений может быть и больше, так как в жизни всякое бывает. Хотя, лучше всего, если вы всё таки будете соблюдать эту рекомендацию хотя бы косвенно. Такой подход вам поможет сэкономить время в будущем.

Хорошо, всё это замечательно, но причём тут вес? — да очень просто, он напрямую от этого зависит. Чем больше вложений, тем больше вес. Логично, однако.

Оценить ваш CSS-код можно с помощью ресурса CSS Specificity Graph Generator. По предложенному вами CSS-коду строится интерактивный график специфичности вашего кода, на котором можно визуально определить проблемные участки ваших стилей.

Выводы

Постарайтесь поддерживать оптимальный вес селекторов и изредка проводите рефакторинг кода. Сначала это кажется избыточным и ненужным, но в итоге это может сыграть с вами плохую шутку.