Selektory#
Specyficzność#
Z wykazu selektorów wynika, że do wyboru elementów utworzono wiele różnych selektorów. Tak naprawdę możliwe jest, by ten sam element został wybrany za pomocą dwóch lub większej liczby różnych selektorów. Wystarczy rozważyć poniższe trzy pary dopasowujące ten sam element:
h1 {color: red;}
body h1 {color: green;}
div.info {color: purple;}
div {color: silver;}
html > body table tr[id="first"] td ul > li {color: moroon;}
li#advice {color: navy;}
Obydwa selektory w każdej parze wybierają identyczny element i próbują ustawić tę samą właściwość. Która z reguł powinna zwyciężyć? Odpowiedzią na to pytanie jest specyficzność (specificity), czyli mechanizm występujący w trzecim kroku kaskady. Specyficzność jest definiowana w następujących dokumentach:
- "CSS 2.1 - Selectors" (rekomendacja)
- "Selectors Level 3" (rekomendacja)
- "Selectors Level 4" (szpic edytorski)
Opisy minimalnie różnią się między sobą, głównie ze względu na pomijanie specyficzność stylu lokalnego bezpośrednio w modułach selektorów (nie ma sensu powielania tych samych informacji w wielu różnych specyfikacjach). Prawda jest taka, że styl lokalny jest silniejszy od stylów zewnętrznych i stylów wewnętrznych (przy założeniu identycznej ważności), dlatego ciągłe wyszczególnianie jego specyficzności nie jest konieczne.
Obliczanie specyficzności#
Specyficzność to liczba, której wartość zależy od budowy selektora. Można ją wyrazić następująco:
a b c
gdzie naliczanie poszczególnych części liczby opisują następujące zasady:
a
- reprezentuje liczbę wszystkich selektorów identyfikatorówb
- reprezentuje liczbę wszystkich selektorów klas, selektorów atrybutów i pseudoklasc
- reprezentuje liczbę wszystkich selektorów typu i pseudoelementów- ignoruj selektora uniwersalnego oraz kombinatory
Po wykonaniu obliczeń liczbę można prezentować na róże sposoby, np. 123
, 1 2 3
, 1,2,3
czy a=1 b=2 c=3
. Format zapisu nie jest z góry narzucany, polecałbym wybierać wariant jak najbardziej czytelny i wygodny w trakcie porównywania (jakiekolwiek separatory między częściami będą wskazane).
Reguły w selektorze o większej specyficzności zwyciężają. Porównywanie dwóch specyficzności odbywa się na zasadzie porównywania ich wyliczonych części składowych: specyficzność z większą wartością a
zwycięża; jeśli dwie wartości a
są równe, to specyficzność z większą wartością b
zwycięża; jeśli dwie wartości b
także są równe, to specyficzność z większą wartością c
zwycięża. Czyli specyficzność 1,0,0
będzie większa od każdej specyficzności rozpoczynającej się od 0
, bez względu na pozostałe części. Specyficzność 0,1,0
wygrywa ze specyficznością 0,0,9
, itd.
Co jeśli obydwie specyficzności są sobie równe (np. 1,0,0
vs. 1,0,0
)? O zwycięstwie decyduje kolejność występowania reguł, która opisywana jest w czwartym kroku kaskady. W uproszczeniu można powiedzieć, że reguła występująca dalej w dokumencie zwycięża.
Ze względu na ograniczenie pamięci, implementacje mogą nakładać restrykcje, co do górnego limitu dla a
, b
lub c
. Jeśli tak będzie, to wartości większe od limitu muszą zostać do niego przycięte, bez wykonania przepełnienia.
Oto kilka przykładowych selektorów z wyliczoną specyficznością:
* /* a=0 b=0 c=0 */
LI /* a=0 b=0 c=1 */
UL LI /* a=0 b=0 c=2 */
UL OL+LI /* a=0 b=0 c=3 */
H1 + *[REL=up] /* a=0 b=1 c=1 */
UL OL LI.red /* a=0 b=1 c=3 */
LI.red.level /* a=0 b=2 c=1 */
#x34y /* a=1 b=0 c=0 */
#s12:not(FOO) /* a=1 b=0 c=1 */
#s12#s12 /* a=2 b=0 c=0 */
P#s12#s12 /* a=2 b=0 c=1 */
#s12#s12.class1.class1 /* a=2 b=2 c=0 */
/* Atrybut lokalny style="" pokonuje wszystkie powyższe */
Należy zauważyć, że powtórzenia tego samego selektora prostego są dozwolone i zwiększają całkowitą specyficzność selektora.
Specyficzność a reguły i deklaracje#
Ustalanie specyficzności odbywa się na podstawie budowy danego selektora. Następnie jest ona przypisywana dla całej reguły oraz wszystkim deklaracjom w tej regule. Rozważmy następującą regułę:
h1 {color: blue; background-color: black;} /* 0,0,1 */
Dla celów specyficzność przeglądarka musi traktować regułę tak, jakby składała się ona z pojedynczych reguł. Z tego powodu powyższy przykład będzie wyglądał następująco:
h1 {color:blue;} /* 0,0,1 */
h1 {backgroud-color: black;} /* 0,0,1 */
Obie reguły mają specyficzność równą 0,0,1
i jest to wartość nadana każdej z deklaracji. Ten sam proces dzielenia ma miejsce w przypadku listy selektorów:
h1, h2.section {color: blue; background-color: black;} /* 0,0,1 */
/* jest traktowane jak */
h1 {color: blue;} /* 0,0,1 */
h1 {backgroud-color: black;} /* 0,0,1 */
h2.section {color: blue;} /* 0,0,1 */
h2.section {backgroud-color: black;} /* 0,0,1 */
Dzięki temu niektóre deklaracje ze słabszej reguły mogą być stosowane do elementu, ale pod warunkiem, że inna reguła o większej specyficzności nie definiuje identycznych deklaracji. Bez takiego rozdzielenia wszystko ze słabszej reguły zostałoby pominięte.
Trzeba zwracać szczególną uwagę na skrócone właściwości. Pominięte właściwości są dodawane domyślnie (z początkowymi wartościami) i posiadają specyficzność jak cała reguła.
Specyficzność a ważność#
Obliczanie specyficzności jest wykonywane przez przeglądarki z automatu. Tak czy inaczej warto wiedzieć, jak taki proces przebiega. Przydaje się to szczególnie w przypadku długich arkuszy stylów. Jest niemal pewne, że trafimy na sytuację, w której dalsze reguły mogą być przesłaniane przez te umieszczone gdzieś na początku arkusza CSS. Zamiast wstawiać bezmyślnie adnotację !important
lepiej po prostu zwiększyć specyficzność selektora.
Przypominam, że specyficzność a ważność to dwa osobne zagadnienia. Ważność (razem z pochodzeniem) występuje w pierwszym kroku kaskady i ma wyższy priorytet niż specyficzność. Dodawanie gdzie popadnie adnotacji !important
może doprowadzić do sytuacji, w której będziemy mieli kłopoty z przesłanianiem własnych reguł. Z tego względu stosowanie ważności polecane jest w ostateczności.
Specyficzność pseudoklas funkcyjnych#
W przypadku pseudoklas funkcyjnych, np. w pseudoklasie dopasowań lub pseudoklasie negacji, specyficzność ustalana jest na innych zasadach.
Specyficzność pseudoklasy :matches()
jest specyficznością najbardziej specyficznego selektora kompleksowego, który pasował. Pełna specyficzność selektora jest równoważna rozwinięciu na zewnątrz wszystkich kombinacji do pełnej postaci, z pominięciem :matches()
.
Specyficzność pseudoklasy :not()
jest specyficznością najbardziej specyficznego selektora kompleksowego w jej liście selektorów.
W obu powyższych przypadkach pseudoklasa we własnej postaci nie wprowadza żadnej dodatkowej specyficzności. Dla przykładu, selektor :matches(em, strong)
ma specyficzność równą (0,0,1)
, podobnie jak selektor typu.
Opisy ustalania specyficzności w przypadku przekazania bardziej złożonych selektorów do tych dwóch pseudoklas nie do końca są dla mnie jasne. Całość zostanie zweryfikowana w przyszłości, kiedy przeglądarki zaczną obsługiwać najnowsze warianty selektorów.
Kilka przykładów z poprawnie wyliczonymi specyficznościami dla pseudoklas funkcyjnych:
:not(div) /* a=0 b=0 c=1 */
p:not(div) /* a=0 b=0 c=2 */
p:not(div):not(div) /* a=0 b=0 c=3 */
p:not(#test) /* a=1 b=0 c=1 */
#test1:not(#test2) /* a=2 b=0 c=0 */
p#test1:not(#test2) /* a=2 b=0 c=1 */
#test1#test1:not([id]) /* a=2 b=1 c=0 */
:not(#test1):not(#test1):not(#test1) /* a=3 b=0 c=0 */
Specyficzność selektora uniwersalnego#
Jak wspomniałem wcześniej, selektor uniwersalny nie ma wpływu na specyficzność selektora. Innymi słowy, ma on specyficzność równą 0,0,0
. Dlatego też dzięki dwóm poniższym regułom akapit zawarty w elemencie div
będzie czarny, natomiast wszystkie inne elementy będą szare:
div p {color: black;} /* 0,0,2 */
* {color: gray;} /* 0,0,0 */
Zatem specyficzność selektora zawierającego selektor uniwersalny oraz inne selektory nie zmienia się ze względu na obecność selektora uniwersalnego. Poniższe dwa selektory mają dokładnie taką samą specyficzność:
div p /* 0,0,2 */
body * strong /* 0,0,2 */
Warto przypomnieć, że selektor uniwersalny reprezentuje wartość zadeklarowaną, która jest przetwarzana w kaskadzie. Kaskada występuje za domyślnością, dlatego selektor uniwersalny przesłoni w elemencie wartość początkową oraz wartość dziedziczoną.
Specyficzność selektorów identyfikatorów i atrybutów#
Należy zwracać uwagę na różnicę specyficzności pomiędzy selektorami identyfikatorów a selektorami atrybutów, których celem jest atrybut id
. Poniższy przykład obrazuje istotę problemu:
html > body table tr[id="first"] td ul > li {color: moroon;} /* 0,1,7 */
li#advice {color: navy;} /* 1,0,1 */
Selektor identyfikatora #advice
w drugiej regule dodaje 1,0,0
do całkowitej specyficzności selektora, i to wystarcza by zwyciężyć pierwszą regułę, w której selektor atrybutu [id="first"]
dodaje jedynie 0,1,0
do całkowitej specyficzność selektora.
W kolejnym przykładzie, biorąc pod uwagę omówione do tej pory zależności, element z atrybutem id
o wartości fire
będzie czerwony:
#fire {color: red;} /* 1,0,0 */
*[id="fire"] {color: green;} /* 0,1,0 */
Podsumowując, selektory identyfikatorów odgrywają istotną rolę w specyficzności (w zasadzie najważniejszą), dlatego warto rozważyć, czy w razie potrzeby nie lepiej byłoby wskazywać elementy za pomocą selektorów atrybutów. Dzięki temu całkowita specyficzność selektora będzie niższa, a ewentualne użycie selektorów identyfikatorów można wykorzystać do awaryjnego "podbicia" specyficzności, bez konieczności głębszej analizy całego arkusza CSS lub dostawiania dyrektywy !important
.