Skąd pochodzą wartości CSS, gdy ustawiasz „None”: jak działa dziedziczenie
Dowiedz się, jak przeglądarki decydują, czy właściwość jest dziedziczona, dlaczego elementy potomne otrzymują obliczoną wartość rodzica oraz w jaki sposób funkcje inherit i initial umożliwiają Ci bezpośrednią kontrolę.
Źródła: How Inheritance Works in CSS Internally at Algorithmic Level.
Ustawiasz color na elementzie otaczającym, a akapit w jego wnętrzu staje się czerwony, mimo że żadna z reguł o tym nie wspomina. Tymczasem odstępy, które dodajesz do tego samego elementu otaczającego, pozostają dokładnie tam, gdzie są. Obie te zachowania wynikają z dziedziczenia w CSS – mechanizmu, który uzupełnia wartość, gdy element nie ma własnej. Ten przewodnik wyjaśnia, które właściwości są dziedziczone, co dokładnie jest przekazywane (odpowiedź zaskakuje wielu osób, gdy chodzi o procenty), jak słowa kluczowe inherit i initial zmieniają domyślne wartości oraz jakie jest miejsce dziedziczenia wśród innych etapów, które przekształcają plik stylów w ostateczne wartości.
Wartość, której nikt nie deklarował
Zacznij od reguły na elemencie nadrzędnym:
.parent {
color: red;
}
oraz odznaczenia, w którym akapit znajduje się wewnątrz tego elementu nadrzędnego:
<div class="parent">
<p>Hello World</p>
</div>
Brak żadnych reguł określających kolor elementu <p>, a mimo to jego tekst jest wyświetlany na czerwono. Zanim przeglądarka dotrze do tego punktu, już rozstrzygnęła spory pomiędzy różnymi deklaracjami poprzez mechanizm kaskady i przetworzyła ich wartości. Pozostaje już tylko decyzja, dla każdej właściwości w każdym elemencie, czy wartość powinna pochodzić z własnych deklaracji elementu, czy z jakiegoś wyższego poziomu drzewa. Dziedziczenie jest mechanizmem, który pomaga w tej decyzji.
Które wartości przechodzą w dół drzewa
Dziedziczenie umożliwia przekazywanie określonych wartości właściwości od elementu rodzica do jego dzieci, a stamtąd dalej do ich potomków:
Parent
↓
Child
↓
Grandchild
Nie we wszystkie właściwości to się odnosi. Właściwości opisujące tekst to klasyczne właściwości dziedziczone, takie jak:
font-family
font-size
color
Dlatego rzadko musisz powtarzać ustawienia typografii w każdym elemencie. Zamiast ustalać tę samą kolorystykę dla całego elementu body, a potem ponownie dla każdego rodzaju podelementu:
body {
color: #333;
}
h1 {
color: #333;
}
p {
color: #333;
}
li {
color: #333;
}
możesz to zadeklarować raz, na górze drzewa dokumentu, i pozwolić dziedziczeniu przenieść te ustawienia w dół. W rezultacie jest mniej kodu CSS oraz jedno miejsce, które należy zmienić w przypadku aktualizacji projektu. Ustawianie podstawowej czcionki i koloru w elementach body lub :root to codzienne zastosowanie tej zasady.
Decyzja przeglądarki dotycząca każdej właściwości
Logika staje się jaśniejsza, gdy przyjrzymy się przypadkowi, w którym żadna deklaracja nie dotyczy danego elementu. Dla każdej właściwości przeglądarka faktycznie zadaje kolejno dwa pytania.
Najpierw: Czy istnieje wartość kaskadowa? Jeśli jakakolwiek deklaracja tej właściwości wygrała kaskadę w tym elemencie, używana jest ta wartość i dziedziczenie nie ma żadnego znaczenia.
Następnie, tylko jeśli odpowiedź brzmi „nie”: Czy ta właściwość jest dziedziczona? To zależy w całości od konkretnej właściwości, ponieważ każda z nich jest zdefiniowana w specyfikacji jako dziedziczona lub niedziedziczona. Oto kilka przykładów właściwości dziedziczonych:
color → inherited
font-family → inherited
font-size → inherited
i kilka, które nie są dziedziczne:
margin → not inherited
padding → not inherited
Rozdzielenie to celowe. Wyobraź sobie świat, w którym właściwość margin jest dziedziczona: jeden margines w sekcji byłby kopiowany do każdego akapitu, listy i linku umieszczonego w jej wnętrzu, a odstępy mnożyłyby się na każdym poziomie. Właściwości związane z elementami graficznymi, takie jak margin, padding, border i width, to niemal zawsze wartości, które chcemy ustawić dla każdego elementu osobno, podczas gdy właściwości tekstowe zazwyczaj mają być stosowane w całym obszarze. Jeśli właściwość nie jest dziedziczona i nie ma wartości kaskadowej, przyjmuje swoją początkową wartość, czyli domyślną wartość zdefiniowaną dla tej właściwości.
Jeśli nie jesteś pewien co do konkretnej właściwości, pozycja „Dziedziczona” w jej dokumentacji referencyjnej odpowiada bezpośrednio na to pytanie.
Dzieci dziedziczą obliczoną wartość, a nie tekst, który napisałeś
To jest najważniejszy detal w całym procesie, a także przyczyna dobrze znanej niespodzianki. Załóżmy, że rodzic ustawia rozmiar czcionki oraz procentową wysokość wiersza:
.parent {
font-size: 20px;
line-height: 150%;
}
a potomek nadpisuje tylko rozmiar czcionki:
.child {
font-size: 25px;
}
Jaka jest ostatecznie wysokość wiersza u potomka? Intuicyjna odpowiedź brzmi, że potomek otrzymuje sam procent:
150%
a następnie dostosowuje go do swojego, większego rozmiaru czcionki:
150% of 25px
Tak to nie działa. Element odziedzicza wartość obliczoną przez rodzica, a nie tę, która została zadeklarowana. W przypadku line-height procent jest przekształcany na długość w momencie obliczania wartości. U rodzica obliczenie wygląda następująco:
150% of 20px = 30px
zatem obliczona wysokość wiersza u rodzica wynosi:
30px
To właśnie ta bezwzględna długość jest przekazywana dalej. Dziecko, mimo większego rozmiaru czcionki, odziedzicza wysokość wiersza równą:
30px
a nie:
150% of 25px
Praktyczną konsekwencją jest to, że wiersze wyglądają ciasno, gdy potomek używa większej czcionki niż element, w którym zadeklarowano procent. To samo dzieje się z wartościami em dla line-height, ponieważ one również są przekształcane na długości przed dziedziczeniem. Właśnie dlatego powszechnie zaleca się użycie wysokości wiersza bez jednostek, np. line-height: 1.5: sam liczbowy wartość jest już wynikiem obliczeń, więc każdy potomek mnoży ją przez swój własny rozmiar czcionki zamiast odziedziczać stałą długość.
Atrybuty, które nie są dziedziczone, wracają do swojej początkowej wartości
A teraz weźmy atrybut z drugiej grupy:
padding
Dodatkowy odstęp nie jest dziedziczony. Dlatego gdy element nadrzędny deklaruje:
.parent {
padding: 20px;
}
element potomny nie otrzymuje go automatycznie:
padding: 20px;
Zamiast tego, przy braku wartości kaskadowej i dziedziczenia, dodatkowy odstęp elementu potomnego używa początkowej wartości tej właściwości, która w przypadku dodatkowego odstępu wynosi:
0px
To obowiązuje, chyba że inne zasady wyraźnie przypiszą elementowi potomnemu inny dodatkowy odstęp. Wiedza o tym, do której kategorii należy dana właściwość, pozwala zaoszczędzić czas podczas debugowania: jeśli wartość wydaje się „przenikać” do elementów potomnych, jest to właściwość dziedziczona; jeśli wydaje się „znikać” u elementów potomnych, jest to właściwość niedziedziczona. Narzędzia deweloperskie przeglądarki pomagają w tym, ponieważ panel Computed może pokazać wartości dziedziczone wraz z elementem nadrzędnym, od którego pochodzą.
Zmuszanie do dziedziczenia za pomocą inherit
Można przejąć kontrolę nad domyślnym zachowaniem dowolnej właściwości. Słowo kluczowe inherit poleca przeglądarce przyjąć wartość elementu nadrzędnego nawet w przypadku właściwości, która normalnie nie zostałaby odziedziczona:
.child {
padding: inherit;
}
Dzięki tej zasadzie wartość paddingu elementu potomnego jest taka sama jak obliczony padding elementu nadrzędnego. Jest to przydatne, gdy chce się, aby związek pomiędzy elementem nadrzędnym a potomnym był wyraźny – na przykład aby element wewnątrz innej struktury ponownie używał koloru obramowania lub odstępów swojego kontenera, albo aby elementy formularza, które zazwyczaj nie odziedziczają czcionek z domyślnych stylów przeglądarki, używały typografii otaczającej je za pomocą font: inherit.
To daje dwie różne drogi do uzyskania wartości nadrzędnej. Automatyczna dziedziczenie dotyczy wyłącznie właściwości odziedziczonych i tylko wtedy, gdy nie ma wartości kaskadowej. Wyraźne dziedziczenie za pomocą inherit jest samym w sobie oświadczeniem, więc przeważa nad kaskadą jak każda inna wartość i działa dla dowolnej właściwości.
Przywracanie do wartości początkowej
Słowo kluczowe to:
initial
Gdy zostanie zastosowane do właściwości, przywraca tę właściwość do jej wartości początkowej zgodnie z specyfikacją:
.child {
color: initial;
}
Należy pamiętać, że „initial” oznacza wartość domyślną określoną w specyfikacji, a nie domyślny plik stylów przeglądarki ani wartość, którą element mógłby w inny sposób odziedziczyć. Dla atrybutu color wartością początkową jest kolor tekstu systemowego (zwykle czarny), dlatego color: initial nie przywraca koloru elementu nadrzędnego – raczej odłącza go od niego. Dla atrybutu display wartością początkową jest inline, stąd display: initial w elemencie div sprawia, że staje się on elementem wierszowym, a nie z powrotem elementem blokowym.
W praktyce te dwa słowa kluczowe pełnią przeciwne funkcje:
inherit → use the parent's value
initial → reset to the property's initial value
Wspólnie umożliwiają one wyraźną kontrolę nad tym, w jaki sposób wartości przemieszczają się po drzewie. CSS oferuje również unset (który zachowuje się jak inherit dla właściwości odziedziczonych i jak initial dla pozostałych) oraz revert (który przywraca wartości do domyślnego pliku stylów przeglądarki); oba te rozwiązania są często bardziej przydatne niż initial do resetowania komponentów.
Budowanie pełnego obrazu
Pomaga spojrzenie na mechanizm dziedziczenia w połączeniu z krokami, które mu poprzedzają. Gdy kilka reguł dotyczy tego samego elementu, przeglądarka zaczyna od kaskady, która porównuje deklaracje w następującej kolejności:
Importance
↓
Specificity
↓
Source Order
Zwycięzcą jest wartość kaskadowa. Następnie wartość przechodzi przez serię etapów przetwarzania, aż stanie się czymś, co może być wykorzystane przez mechanizmy układu i rysowania:
Declared Value
↓
Cascaded Value
↓
Specified Value
↓
Computed Value
↓
Used Value
↓
Actual Value
Gdy nie istnieje wartość kaskadowa, dziedziczenie dostarcza zamiast niej dane do tej sekwencji:
No Cascaded Value
↓
Can Property Be Inherited?
↓
Yes
↓
Parent's Computed Value
↓
Child
Ściśle mówiąc, specyfikacja umieszcza dziedziczenie w momencie określenia wartości określonej: wartość określona to wartość kaskadowa, jeśli taka istnieje; w przeciwnym razie jest to obliczona wartość rodzica dla właściwości odziedziczonych; jeszcze w innym przypadku jest to wartość początkowa. Od tego momentu kontynuują się standardowe etapy obliczania, wykorzystywania i faktycznego zastosowania wartości. Uproszczony przegląd etapu przetwarzania CSS wygląda zatem w ten sposób:
CSS
↓
Cascade
↓
┌─────────────┴─────────────┐
↓ ↓
Importance Specificity
↓
Source Order
↓
Cascaded Value
↓
Value Processing
↓
Computed / Used / Actual
↓
Inheritance
↓
CSSOM
Należy postrzegać pionową kolejność na tym diagramie jako grupowanie koncepcji, a nie ściśłą chronologię, ponieważ dziedziczenie dostarcza określoną wartość, zamiast działać po obliczeniu wartości. Kluczowa idea pozostaje ta sama: między plikiem stylów a ekranem dzieje się o wiele więcej niż tylko „napisałem regułę, a przeglądarka ją zastosowała”.
Gdy przyjrzymy się temu z większej odległości, oto pełna ścieżka z rozszerzonym etapem CSS:
HTML
↓
DOM
↓
CSS
↓
Cascade
↓
Specificity
↓
Value Processing
↓
Inheritance
↓
CSSOM
↓
DOM + CSSOM
↓
Render Tree
↓
Layout
↓
Paint
↓
Pixels
Biorąc pod uwagę tę ścieżkę przetwarzania, nawet mała reguła wiąże się z dużą ilością domyślnych działań:
.title {
font-size: 2rem;
color: blue;
}
Zanim cokolwiek zostanie narysowane, przeglądarka musi ustalić:
- kterdeklaracje odnoszą się do każdego elementu;
- która z nich ma pierwszeństwo w przypadku konfliktu;
- jak specyficzne są konkurencyjne selektory;
2rem w pikselach);Dopiero po tym wszystkim strona przechodzi do układu i renderowania.
CSS jako system, a nie lista zasad
Znaczna część zamieszania wokół CSS znika, gdy przestaniemy traktować je jako pojedyncze zasady i zaczniemy postrzegać je jako jeden system o jasnych obowiązkach:
- Specyficzność wyjaśnia, dlaczego jedna deklaracja ma pierwszeństwo przed drugą.
- Kolejność źródeł rozstrzyga w przypadku remisów, gdy specyficzność jest taka sama.
- Przetwarzanie wartości przekształca to, co napisałeś, w wartości, których może użyć przeglądarka.
- Dziedziczenie wyjaśnia, skąd pochodzą wartości, gdy nie określiłeś ich sam.
Razem przekształcają one arkusz stylów w kompletny zestaw wartości końcowych, które są wykorzystywane do układu i rysowania. Nawet tak krótka deklaracja jak ta:
width: 66%;
przechodzi przez wszystkie te etapy. Przeglądarka określa, która zasada ma zastosowanie, przetwarza wartość procentową, odnosi ją do bloku nadrzędnego i ustala ostateczną szerokość, wszystko to zanim pojawi się choćby jeden piksel.
To, co pozostaje po tym procesie, to odrębne pytanie: dokładnie gdzie ma się znaleźć każdy element. Szerokości, wysokości, normalny przepływ treści, pozycjonowanie oraz model ramki należą do modelu formatowania wizualnego, kolejnego etapu w tym procesie, który bierze te wartości końcowe i przekształca je w układ.
Główne wnioski
- Wartość właściwości pochodzi najpierw z łańcucha reguł; dziedziczenie jest sprawdzane tylko wtedy, gdy nie ma żadnej odpowiedniej deklaracji.
- Każda właściwość jest zdefiniowana jako dziedziczona (głównie związana z tekstem:
color,font-*) lub niedziedziczona (głównie związana z obszarem elementu:margin,padding). - Dzieci elementów dziedziczą obliczoną wartość rodzica, więc wysokości wiersza wyrażone procentami lub
emprzechodzą jako stałe wartości; lepiej używaćline-heightbez jednostek. - Właściwości niedziedziczone, których nie zadeklarowano, używają swojej początkowej wartości, na przykład
0dla paddingu. inheritzmusza do dziedziczenia dowolnej właściwości;initialprzywraca wartość domyślną z specyfikacji, a nie wartość rodzica;unsetirevertsą często lepszymi narzędziami do przywracania ustawień.
Literatura pokrewna
- Dziewięć technik tuningu w stylu ciemnym porównane, od haków filtrów po pliki cukiernicze serwera — Porównaj dziewięć sposobów dodania trybu ciemnego do aplikacji internetowej, od odwracania filtrów po tokeny, funkcję light-dark() oraz pliki cukiernicze serwera, i dowiedz się, jakie błędy potajemnie wprowadza każda z nich.
- Przepłynięcie, przedrukowanie, składanie: ile kosztuje brausera każda zmiana w CSS — Prześledź działanie HTML i CSS w DOM, CSSOM, układzie, procesie malowania oraz składania elementów, i dowiedz się, dlaczego zmiany szerokości kosztują więcej niż zmiany kolorów oraz jak uniknąć problemów z układem.