Откуда берутся значения CSS при установке None: как работает наследование
Узнайте, как браузеры определяют, наследуется ли свойство, почему дочерние элементы получают вычисленное значение родителя, и как методы inherit и initial позволяют осуществлять явный контроль.
Источники: How Inheritance Works in CSS Internally at Algorithmic Level.
Вы устанавливаете свойство color для обёртки, и абзац внутри становится красным, хотя ни одно правило этого не предусматривало. В то же время отступы, заданные для той же обёртки, остаются на прежнем месте. Оба этих поведения обусловлены наследованием в CSS — механизмом, при котором задаётся значение, если у элемента его собственного нет. В этом руководстве объясняется, какие свойства наследуются, что именно передаётся дальше (ответ удивляет многих, когда речь идёт о процентах), как ключевые слова inherit и initial изменяют стандартные значения, и какое место занимает наследование среди других этапов, превращающих стильовую таблицу в окончательные значения.
Значение, которое никто не объявлял
Начнём с правила для родительского элемента:
.parent {
color: red;
}
и с маркировкой, в которой абзац находится внутри этого родительского элемента:
<div class="parent">
<p>Hello World</p>
</div>
Никакие правила не задают значение color для тега <p>, однако текст отображается красным. К моменту, когда браузер достигает этой части кода, он уже обработал все конфликтующие объявления с помощью механизма каскадирования и определил их значения. Остаётся лишь решить для каждого свойства каждого элемента, должно ли значение браться из собственных объявлений элемента или из более высоко расположенных в иерархии элементов. Наследование — это механизм, который помогает принимать такие решения.
Какие значения передаются по иерархии
Наследование позволяет определённым значениям свойств передаваться от родительского элемента к его дочерним элементам, а затем от них — к их дочерним элементам:
Parent
↓
Child
↓
Grandchild
Не все свойства участвуют в процессе наследования. Свойства, отвечающие за оформление текста, являются классическими примерами свойств, наследуемых от родительских элементов, такими как:
font-family
font-size
color
Вот почему редко бывает необходимость повторять настройки типографики для каждого элемента. Вместо того чтобы устанавливать один и тот же цвет для всего контента, а затем снова — для каждого типа дочерних элементов:
body {
color: #333;
}
h1 {
color: #333;
}
p {
color: #333;
}
li {
color: #333;
}
их можно задать один раз, высоко в иерархии документа, и позволить механизму наследования передавать эти значения дальше. В результате используется меньше CSS, и изменения дизайна нужно вносить только в одном месте. Установка базового шрифта и цвета для body или :root — это повседневное применение этой идеи.
Решение браузера для каждого свойства
Логика становится понятнее, если рассмотреть ситуацию, когда никакая настройка не применяется к элементу. Для каждого свойства браузер фактически по порядку задает два вопроса.
Во-первых: существует ли каскадируемое значение? Если какое-либо объявление этого свойства победило в каскадировании для данного элемента, используется именно это значение, и наследование не применяется.
Во-вторых, только если ответ «нет»: наследуется ли это свойство? Это полностью зависит от самого свойства, поскольку каждое из них согласно спецификации определяется как наследуемое или не наследуемое. Некоторые примеры наследуемых свойств:
color → inherited
font-family → inherited
font-size → inherited
а также некоторые, которые не наследуются:
margin → not inherited
padding → not inherited
Такой подход сознательный. Представьте мир, в котором свойства margin наследуются: один маржинальный отступ для раздела копировался бы в каждый абзац, список и ссылку, находящиеся внутри него, что приводило бы к увеличению отступов на каждом уровне. Свойства, связанные с контейнерами, такие как margin, padding, border и width, почти всегда требуют индивидуального задания для каждого элемента, в то время как свойства, связанные с текстом, обычно должны применяться ко всей области. Если свойство не наследуется и у него нет каскадируемого значения, оно берет своё начальное значение, заданное по умолчанию для этого свойства.
Если вы не уверены относительно конкретного свойства, раздел «Наследуется» в его документации поможет прямо ответить на этот вопрос.
Дочерние элементы наследуют вычисленное значение, а не написанный вами текст
Это самая важная деталь всего процесса, лежащая в основе известного сюрприза. Предположим, родитель задает размер шрифта и процентное значение высоты строки:
.parent {
font-size: 20px;
line-height: 150%;
}
а дочерний элемент переопределяет только размер шрифта:
.child {
font-size: 25px;
}
Какова же в итоге высота строки у дочернего элемента? Интуитивно кажется, что он получает сам процент:
150%
а затем применяет его к своему собственному, большему размеру шрифта:
150% of 25px
Но всё не так. Элемент наследует вычисленное значение своего родителя, а не то значение, которое было задано изначально. Для атрибута line-height процентное значение преобразуется в конкретную длину в момент вычисления значений. У родителя расчет выглядит следующим образом:
150% of 20px = 30px
следовательно, вычисленная высота строки у родителя составляет:
30px
Именно этая абсолютная длина и передаётся дальше. Потомок, несмотря на более крупный шрифт, наследует высоту строки:
30px
а не:
150% of 25px
Практическим последствием является то, что строки выглядят тесными каждый раз, когда потомок использует более крупный шрифт, чем у элемента, в котором было указано процентное значение. То же самое происходит с значениями em для line-height, поскольку они также преобразуются в длины перед наследованием. Именно поэтому рекомендуется использовать высоты строк без единиц измерения, такие как line-height: 1.5: простое число само по себе является вычисленным значением, так что каждый потомок умножает его на свой собственный размер шрифта вместо того, чтобы наследовать фиксированную длину.
Свойства, которые не наследуются, возвращаются к своему начальному значению
Теперь рассмотрим свойство из другой группы:
padding
Параметр padding не наследуется. Поэтому когда родительский элемент объявляет:
.parent {
padding: 20px;
}
потомок не автоматически получает его значения:
padding: 20px;
Вместо этого, при отсутствии каскадирования значений и наследования, параметр padding потомка использует начальное значение свойства, которым для padding является:
0px
Это значение сохраняется, пока какое-либо другое правило явно не установит для потомка другое значение padding. Знание того, в какую категорию относится свойство, экономит время при отладке: если значение, кажется, «проникает» в потомки, это наследуемое свойство; если оно, кажется, «исчезает» у потомков, это ненаследуемое свойство. Для этого полезны инструменты разработчика браузера, поскольку во вкладке Computed можно увидеть наследуемые значения вместе с их источником — родительским элементом.
Принудительное наследование с помощью inherit
Вы можете изменить стандартное поведение любого свойства. Ключевое слово inherit указывает браузеру использовать значение родителя даже для свойства, которое обычно не наследуется:
.child {
padding: inherit;
}
Благодаря этому правилу значение отступов дочернего элемента совпадает с вычисленными отступами родительского элемента. Это полезно, когда необходимо четко определить взаимосвязь между родителем и дочерним элементом, например, чтобы вложенный элемент использовал цвет границы или отступы своего контейнера, либо чтобы элементы формы, которые по умолчанию в стилях браузера не наследуют шрифты, использовали шрифт окружающего текста с помощью font: inherit.
Это предоставляет два различных способа получения значения родительского элемента. Автоматическое наследование применяется только к унаследованным свойствам и лишь тогда, когда отсутствует каскадирование значений. Явное наследование с использованием ключевого слова inherit само по себе является объявлением, поэтому оно имеет приоритет перед каскадированием, как и любое другое значение, и работает с любыми свойствами.
Сброс к начальному значению
Сопутствующее ключевое слово —:
initial
При применении к свойству оно сбрасывает это свойство к его начальному значению, определенному спецификацией:
.child {
color: initial;
}
Следует помнить, что понятие «initial» означает значение по умолчанию, указанное в спецификации, а не стилишит по умолчанию браузера или значение, которое элемент мог бы иначе унаследовать. Для свойства color начальное значение — цвет текста системы (обычно чёрный), поэтому указание color: initial не восстанавливает цвет родителя элемента; оно отделяет элемент от него. Для свойства display начальное значение — inline, именно поэтому указание display: initial для элемента div превращает его в элемент типа inline, а не возвращает к формату блока.
Если рассматривать эти два ключевых слова одновременно, они выполняют противоположные функции:
inherit → use the parent's value
initial → reset to the property's initial value
Вместе они позволяют полностью контролировать процесс передачи значений по дереву стилей. CSS также предоставляет функции unset (которая ведёт себя как inherit для унаследованных свойств и как initial для остальных) и revert (которая возвращает элемент к стандартному шаблону стилей браузера); обе эти функции часто более полезны, чем initial, для сброса настроек компонентов.
Полная картина
Полезно рассматривать механизм наследования на фоне предшествующих ему этапов. Когда несколько правил применяются к одному и тому же элементу, браузер начинает с каскадирования, при котором объявления сравниваются в следующем порядке:
Importance
↓
Specificity
↓
Source Order
Победителем становится каскадированное значение. Затем это значение проходит через серию этапов обработки, пока не превратится в то, что может быть использовано для формирования макета и отрисовки:
Declared Value
↓
Cascaded Value
↓
Specified Value
↓
Computed Value
↓
Used Value
↓
Actual Value
Когда каскадируемое значение отсутствует, наследование предоставляет входные данные для этой последовательности вместо него:
No Cascaded Value
↓
Can Property Be Inherited?
↓
Yes
↓
Parent's Computed Value
↓
Child
Строго говоря, спецификация предусматривает применение наследования в том месте, где определяется указанное значение: указанным значением является каскадируемое значение, если оно существует; в противном случае — вычисленное значение родительского элемента для наследуемых свойств; ещё в противном случае — начальное значение. После этого продолжаются обычные этапы вычисления, использования и фактического применения значения. Упрощённый общий вид этапа обработки CSS выглядит следующим образом:
CSS
↓
Cascade
↓
┌─────────────┴─────────────┐
↓ ↓
Importance Specificity
↓
Source Order
↓
Cascaded Value
↓
Value Processing
↓
Computed / Used / Actual
↓
Inheritance
↓
CSSOM
В этой диаграмме вертикальный порядок следует рассматривать как группировку концепций, а не как строгую временную шкалу, поскольку наследование задаёт указанное значение, а не действует после вычисления значений. Основная идея остаётся прежней: между стилишитом и экраном происходит гораздо больше, чем просто «я написал правило, и браузер его применил».
Если смотреть в целом, вот полный процесс с расширенным этапом CSS:
HTML
↓
DOM
↓
CSS
↓
Cascade
↓
Specificity
↓
Value Processing
↓
Inheritance
↓
CSSOM
↓
DOM + CSSOM
↓
Render Tree
↓
Layout
↓
Paint
↓
Pixels
Учитывая эту цепочку обработки, даже небольшое правило подразумевает выполнение множества действий:
.title {
font-size: 2rem;
color: blue;
}
Прежде чем что-либо отрисовать, браузер должен определить:
- какие объявления применимы к каждому элементу;
- какое из них имеет приоритет в случае конфликта;
- насколько специфичны конкурирующие селекторы;
2rem);Только после этого страница переходит к формированию макета и отрисовке.
CSS как система, а не список правил
Большая часть путаницы вокруг CSS исчезает, как только вы перестаёте рассматривать её как набор изолированных правил и начинаете видеть в ней единую систему с чёткими функциями:
- Специфичность объясняет, почему одно заявление имеет приоритет перед другим.
- Порядок источников разрешает ситуации равенства специфичности.
- Обработка значений преобразует написанное вами в значения, которые может использовать браузер.
Вместе они превращают таблицу стилей в полный набор окончательных значений, которые используются для формирования макета и отрисовки. Даже такое краткое заявление:
width: 66%;
проходит через все эти этапы. Браузер определяет, какое правило применимо, обрабатывает процентное значение, связывает его с контейнерным блоком и определяет окончательную ширину, причем всё это происходит еще до того, как появится хотя бы один пиксель.
После этого возникает отдельный вопрос: куда именно будет размещаться каждый элемент. Ширины, высоты, нормальный поток отрисовки, позиционирование и модель элемента относятся к визуальной модели форматирования — следующему этапу процесса, который берет эти окончательные значения и преобразует их в макет.
Основные выводы
- Значение свойства сначала берется из каскада стилей; наследование применяется только тогда, когда не существует соответствующего заявления.
- Каждое свойство определяется как наследуемое (в основном связанное с текстом:
color,font-*) или ненаследуемое (в основном связанное с контейнером:margin,padding). - Дочерние элементы наследуют вычисленное значение родителя, поэтому процентные значения или значения
emдля высоты строки передаются в виде фиксированных длин; предпочтительнее использовать свойствоline-heightбез единиц измерения. - Свойства, которые не наследуются и для которых нет специального указания, используют своё начальное значение, например
0для padding. inheritпринудительно включает наследование для любого свойства;initialвозвращает значение по умолчанию, указанному в спецификации, а не значение родителя;unsetиrevertчасто являются более подходящими инструментами для сброса настроек.