Strona główna / Artykuły

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ę.

Wydawca: REACTAPP.TOP10 min czytania2243 słów

Ź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;
  • jaka wartość faktycznie wynika z każdej deklaracji zwycięskiej (na przykład, co oznacza 2rem w pikselach);
  • czy dziedziczenie dostarcza wartości, które nie zostały zadeklarowane;
  • jakie rozmiary i pozycje ostatecznie wynikają z tych wartości.
  • 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 em przechodzą jako stałe wartości; lepiej używać line-height bez jednostek.
    • Właściwości niedziedziczone, których nie zadeklarowano, używają swojej początkowej wartości, na przykład 0 dla paddingu.
    • inherit zmusza do dziedziczenia dowolnej właściwości; initial przywraca wartość domyślną z specyfikacji, a nie wartość rodzica; unset i revert są często lepszymi narzędziami do przywracania ustawień.

    Literatura pokrewna

  • Od 66% do 185px: Jak przeglądarki rozwiązują wartości CSS przed ułożeniem — Prześledź wartość CSS na wszystkich etapach: deklarowanej, kaskadowej, określonej, obliczonej, użytej i faktycznej, oraz dowiedz się, dlaczego jednostki względne i trik konwersji rem zachowują się w taki sposób.
  • Jak kaskada wybiera zwycięzcę: Znaczenie, specyficzność i kolejność źródła — Zobacz, jak przeglądarki rozstrzygają między sprzecznymi deklaracjami CSS, jak interpretować specyficzność jako porównanie czterech elementów oraz dlaczego reguły hover i !important tak często nas zaskakują.
  • Od skryptu stylów do ekranu: Gdzie CSS mieści się w procesie brauzera — Śledź los CSS od pobrania do pikseli: jak budowane są DOM, CSSOM i drzewo renderowania, gdzie odbywa się kaskada stylów oraz jakie źródła stylów konkurują o każdy element.