Каскад, наследование и специфичность селекторов в CSS

Каскад, наследование и специфичность селекторов в CSS
Содержание:
  1. Наследование стилей
  2. Начальное значение CSS-свойства
  3. Источник (происхождение) CSS
  4. Специфичность селекторов
  5. Что такое каскад в CSS?
  6. Комментарии

Цель этой статьи – изучить принципы основополагающих концепций CSS, таких как наследование, происхождение стилей, специфичность селекторов и каскад.

Наследование стилей

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

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

Смотрим на w3.org является ли свойство color наследуемым

Да, свойство color по умолчанию является наследуемым. А это значит, что браузеру необходимо просто взять значение этого свойства у родительского элемента. В этом и заключается в принципе вся суть наследования.

Как определить значение наследуемого свойства конкретного элемента? Для этого следует найти первый элемент, для которого данное свойство задано. При этом поиск необходимо выполнять по цепочке вложенности элементов, но в обратном порядке. В этой цепочке сначала идёт сам элемент, далее родитель, затем родитель его родителя и так далее.

Как уже отмечали выше, в CSS по умолчанию наследуются не все свойства, а только некоторые из них. Вот список наиболее популярных наследуемых свойств:

  • для шрифтов: font, font-style, font-weight, font-size и font-family, font-variant, line-height;
  • для текста: text-align, text-transform, text-indent, text-shadow;
  • для границ: border-collapse, border-spacing;
  • для оформления списков: list-style, list-style-type, list-style-position и list-style-image;
  • для установки расстояний между символами и словами: letter-spacing, word-spacing и white-space;
  • для установки типа курсора: cursor;
  • для управления видимостью элемента: visibility.

Рассмотрим на примере, как работает наследование. Для этого установим цвет текста для <body>:

CSS
body {
  color: red;
}

В результате, этот цвет будет не только у <body>, но и у всех вложенных в него элементов, если им не установлено какое-то другое значение color.

Смотрим на w3.org является ли свойство color наследуемым

Установим цвет ещё для <main>:

CSS
body {
  color: red;
}

main {
  color: black;
}

Теперь, весь текст, который находится в <main> будет окрашен в чёрный цвет. Так как на уровне этого элемента мы установили свойству color новое значение и тем самым переопределили наследуемое им значение color от <body>.

Смотрим на w3.org является ли свойство color наследуемым

Кроме этого, при необходимости мы можем явно позаимствовать значение свойства у родителя. Осуществляется это с помощью ключевого слова inherit:

CSS
body {
  border: 1px solid black;
}

h1 {
  border: inherit;
}

В этом примере <h1> будет иметь ту же границу, что и элемент <body>, в котором он непосредственно расположен.

Итак, наследование – это процесс передачи стилей от родительского элемента его детям. При этом по умолчанию наследуются не все свойства, а только определённые. Кроме этого, в CSS имеется ключевое слово inherit. С помощью него мы можем сделать любое свойство наследуемым. В этом случае это значение будет браться у родителя. Если у родителя оно тоже имеет значение inherit, то браузер продолжит подниматься вверх по DOM до тех пор, пока не найдёт какое-нибудь значение. В случае, если мы достигли корневого элемента документа (<html>) и там нет установленного значения, то тогда будет использоваться начальное значение CSS-свойства.

Начальное значение CSS-свойства

Каждое CSS-свойство имеет определённое начальное значение (initial value). Посмотреть какое начальное значение имеет то или иное свойство можно в спецификации.

Например, для color начальное значение в спецификации обозначено как «depends on user agent»:

Смотрим на w3.org является ли свойство color наследуемым

Это значит, что начальное значение color определяется пользовательским агентом (браузером). А, следовательно, в разных браузерах оно может быть различным. Например, в Chrome свойство color имеет начальное значение rgb(0, 0, 0):

Смотрим на w3.org является ли свойство color наследуемым

Если пролистать документацию дальше, то увидим свойство opacity. Это свойство в качестве значения по умолчанию имеет 1:

Смотрим на w3.org является ли свойство opacity наследуемым

Так зачем вообще нужны эти начальные значения? Чтобы понять это, подумайте, что нужно знать браузеру, чтобы отрисовать страницу на экране? Правильно, знать значение каждого CSS-свойства каждого элемента на странице. А их, кстати, только у одного элемента несколько сотен. Поэтому чтобы не устанавливать каждое свойство каждому элементу на странице и нужны начальные значения.

Тогда, каким образом вычисляется значение некоторого свойства элемента, если ему явно не установлено значение?

Тут всё просто и зависит от того, является ли данное свойство наследуемым. Для наследуемого свойства значение будет браться из родительского элемента, а для ненаследуемого – начальное.

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

Кроме этого, явно установить свойству начальное значение, можно с помощью ключевого слова initial:

HTML
<style>
  p {
    color: blue;
  }
  
  strong {
    color: initial;
  }
</style>

<p>
  <span>Этот текст синего цвета.</span>
  <strong>Этот текст имеет начальное значение цвета (обычно чёрный).</strong>
  <span>Этот текст снова синего цвета.</span>
</p>

Источник (происхождение) CSS

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

Таким образом при конфликте стилей, браузер сначала пытается определить какое из них более приоритетно в зависимости от их происхождения и важности.

Происхождение и важность правил (чем ниже — тем приоритетней):

  1. Стили пользовательского агента (браузера). Это стили, которые браузер применяет к HTML-элементам по умолчанию.
  2. Пользовательские стили. Это стили, которые сам пользователь может прописать для сайтов. Сейчас такую возможность практически никакие браузеры не предоставляют. Но она осталась доступной посредством расширений. Например, в Chrome таким расширением является Stylish. Но делать это крайне не рекомендуется, так как данные действия могут привести к поломкам сайтов.
  3. Авторские стили. Это стили, которые загружаются с сервера. Их для сайта создаёт веб-разработчик.
  4. Стили во время выполнения анимации (@keyframe, animation).
  5. Авторские стили с !important.
  6. Пользовательские стили с !important.
  7. Стили пользовательского агента (браузера) с !important.
  8. Стили во время CSS-перехода (transition).

Этот порядок означает, что авторский CSS-код имеет приоритет над стилями пользователя и браузера, если они конечно заданы без !important. Анимации более специфичны обычных значений вне зависимости от того, где они объявлены. Значения с !important имеют приоритет над анимацией, а переходы – над важными значениями.

Например, в авторском CSS-коде и стилях браузера имеется значение с !important. Какое из них выиграет? Да, которое находится в стилях браузера, так как в данном случае оно более приоритетно.

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

Специфичность селекторов – это ещё один алгоритм, к которому прибегает браузер для разрешения конфликтных ситуаций. Он используется, когда процедура происхождения и важности не даёт окончательного результата и на выходе остаётся ещё несколько конкурирующих между собой правил. Например, из всех правил самые значительные расположены в авторских стилях. И в них находится не одно правило, а несколько и все они пытаются установить одно и тоже CSS-свойство некоторому элементу.

Как раз для разрешения таких случаев и нужен алгоритм специфичности селекторов.

Рассмотрим два CSS-правила. Одно красит текст <p> в синий цвет, а другое – в красный. Каким в результате будет цвет?

CSS
body p {
  color: blue;
}

p {
  color: red;
}

В этом примере селектор body p более специфичен, чем p. Это означает, что все абзацы <p> внутри <body> будут иметь синий цвет текста несмотря на то, что правило с селектором p идёт ниже.

Если же из первого правила убрать селектор body, то эти два правила будут иметь одинаковую специфичность, и, следовательно, из них будет применено то, которое записано в коде ниже. В этом случае текст в <p> будет иметь красный цвет.

Специфичность селекторов очень удобно представлять в виде чисел. При этом баллы приносит каждый тип селектора CSS-правила. В результате, чтобы посчитать общую специфичность селектора нужно будет просто сложить все эти баллы.

Таким образом, специфичность селектора можно представить как сумму баллов, который даёт каждый тип селектора в его составе.

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

Как считать эти баллы? Каждый селектор в зависимости от типа даёт определённое количество баллов:

  • универсальный селектор (*) не имеет специфичности и даёт 0 баллов;
  • селектор тега или псевдоэлемента – 1 балл;
  • селектор класса, псевдокласса или атрибута – 10 баллов;
  • селектор идентификатора – 100 баллов;
  • стили, добавленные через атрибут style – 1000 баллов;
  • правило !important в конце значения – 10000 баллов.

Примеры селекторов и их специфичность:

  • *0;
  • li1;
  • li::before2;
  • ul > li2;
  • div input+label3;
  • h1 + div[data-target]12;
  • .btn.show20;
  • ul li a.item13;
  • #aside div.show111;
  • style="..."1000;

Например, имеется три правила. Каким будет цвет текста <p>:

HTML
<style>
  #red {
    color: red;
  }

  .green {
    color: green;
  }

  p {
    color: black;
  }
</style>

<p id="red" class="green">...</p>

Красным, потому что селектор #red более специфичен, чем другие селекторы. Он даёт 100 баллов. Но, если селектор p мы изменим на p#red, то теперь он будет более специфичен (101 балл) и в результате цвет текста будет черный.

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

С помощью ключевого слова !important можно очень сильно повысить важность:

CSS
<style>
  .alert {
    background-color: #ffa000 !important;
  }
</style>

<div class="alert" style="background-color: #ffc107;"> ... </div>

В этом примере цвет фона элемента будет определяться свойством background-color, к которому добавлено !important, так как его вес в данном случае составляет 10010 баллов. Это количество баллов больше, чем имеет свойство background-color в <style> (1000 баллов).

Но, если это значение нужно перебить, то можно добавить !important к CSS-свойству background-color, расположенному в style:

CSS
<div class="alert" style="background-color: #ffc107 !important;"> ... </div>

В этом случае мы получим 11000 баллов. 10000 даёт !important и 1000 – атрибут <style>. Это максимально возможная специфичность, которую перебить каким-то другим образом уже нельзя.

В реальных проектах использовать !important не рекомендуется, так как это может привести к усложнению кода CSS и его последующей поддержки.

Что такое каскад в CSS?

CSS, как вы уже знаете — это каскадные таблицы стилей. Но, мы до сих пор ещё не разобрали, что же такое каскад.

Каскад — это алгоритм, в соответствии с которым разрешаются конфликты, когда к HTML-элементу применяются несколько CSS-правил. Он, например, является причиной тому, почему текст кнопки, стилизованной с помощью следующего кода CSS, будет красным:

CSS
button {
  color: black;
}

button {
  color: red;
}

Знание принципа каскада поможет вам понять, почему в некотором случае применяются одни стили, а в ином – другие.

Суть каскада основана на следующих принципах:

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

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

Кроме этого, также важен порядок CSS-свойств в самом правиле. Если вы в одном правиле указали несколько одиноковых свойств, то более приоритетным из них будет последнее:

CSS
p {
  font-size: 1.5rem;
  font-size: clamp(1.5rem, 1rem + 3vw, 2rem);
}

Эту возможность в основном используют для создания запасных вариантов, чтобы обеспечить поддержку различных браузеров.

Например, в этом примере мы объявили font-size дважды. Когда функция clamp() поддерживается браузером, предыдущее объявление font-size будет перезаписано новым значением. В противном случае (если clamp() не поддерживается), это значение будет проигнорировано браузером и размер шрифта будет равен 1.5rem.

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

Комментарии: 2

MrakCw
MrakCw

Nice article

Thanks
Александр Мальцев
Александр Мальцев

Thank you ❤. Keep reading.