Звідки беруться значення 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
Падіння елемента не успадковується. Тож коли батьківський елемент оголошує:
.parent {
padding: 20px;
}
дочірній елемент не отримує цього автоматично:
padding: 20px;
Натомість, через відсутність каскадування значень та успадкування, падіння дочірнього елемента використовує початкове значення властивості, яке для падіння є таким:
0px
Це залишається таким, доки якась інша правило не надасть дочірньому елементу інше значення падіння. Знання того, до якої категорії належить властивість, допомагає економити час під час відлагоджування: якщо значення, здається, „протікає“ до дочірніх елементів, це успадкована властивість; якщо воно, здається, „зникає“ у дочірніх елементах, це невспадкована властивість. Інструменти розробника браузера допомагають у цьому, адже панель 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часто є кращими інструментами для скидання налаштувань.