Główne zdjęcie witryny
Kocha się za nic. Nie istnieje żaden powód do miłości. – Paulo Coelho

Specyficzność i precyzja selektorów CSS

Logo CSS3

Dokonując stylizacji strony internetowej zapewne doskonale wiesz, że najwyższy priorytet mają reguły CSS osadzone lokalnie (np. za pomocą atrybutu style="color:red;". Następnie brane są pod uwagę wewnętrzne arkusze stylów umieszczane wewnątrz znaczników <style></style>, a na końcu zewnętrzne arkusze stylów dołączane w postaci plików *.css, które są najbardziej popularne. Jednak w tych dwóch ostatnich – których stosowanie nawet jest bardziej polecane – często możemy spotkać się z różnymi, że tak powiem z ludzkiego punktu widzenia „kolizjami” selektorów CSS. To, że jeden selektor następuje po poprzednim nie zawsze musi go przesłaniać i być zastosowany! W tym poradniku pomogę Ci na przykładach zrozumieć dlaczego tak się dzieje i wyjaśnić związane z tym pojęcie specyficzności (inaczej precyzji) stosowanej w kaskadowych arkuszach stylów oraz jak radzić sobie w takich przypadkach i uniknąć niepotrzebnych problemów (i niepotrzebnych nerwów).

Wprowadzwenie

Specyficzność (dalej nazywana precyzją) CSS określa pierszeństwo stosowania reguł CSS. Każdy selektor, który zawiera daną regułę, posiada odpowiadającą mu precyzję (inaczej ważność). Selektor o wyższej precyzji będzie przesłaniał (lub nadpisywał) selektor o niższej precyzji. Aby bardziej zrozumieć czym jest precyzja selektora przyjrzyjmy się poniższym przykładom:

p { font-size: 14pt; }
p.largetext { font-size: 16pt; }
<p class="largetext">Jakiś tam tekst...</p>

Jak się nietrudno domyślić, pierwsza reguła CSS zostanie przesłonięta przez drugą i w konsekwencji zostanie wyświetlony tekst o rozmiarze 16pt (a nie 14pt). Może wydawać się to oczywiste, ale co się stanie gdy zamienimy kolejnością wspomniane reguły, tak jak pokazałem poniżej?

p.largetext { font-size: 16pt; }
p { font-size: 14pt; }

Jak się okazuje, nie ma tutaj znaczenia kolejność zdefiniowanych reguł – został zastosowany większy rozmiar czcionki, mimo, że reguła ta została zapisana przed tą drugą (o mniejszej czcionce). Obie reguły mają różną precyzję, stąd to zjawisko. Dlatego warto zapamiętać:

Selektor z przypisaną klasą jest bardziej precyzyjny i przesłania prostszą regułę, która tej klasy nie zawiera, bez znaczenia kolejności ich wystąpienia.

Oczywiście, jeśli znajdą się przynajmniej dwa selektory o identycznej precyzji, zostanie zastosowana reguła ostatniego wymienionego selektora.

Jak się ją wyznacza

Precyzyjność zależna jest od liczby znaczników, klas i identyfikatorów w selektorze. Istnieje prosty sposób obliczania precyzji dowolnego selektora, który oparty jest na wzorze I–C–E (myślniki w tym wzorze nie są znakami odejmowania tylko separatorami poszczególnych wartości), który działa następująco:

  1. Dodaj jeden do wartości I (ang. ID) za każdy identyfikator występujący w selektorze
  2. Dodaj jeden do wartości C (ang. Class) za każdą klasę występującą w selektorze
  3. Dodaj jeden do wartości E (ang. Element) za każdy element (znacznik) występujący w selektorze

Aby obliczyć precyzję danego selektora należy złączyć (nie dodać) trzy uzyskane powyżej wartości według powyższej kolejności (I ma największą wagę, C średnią, E najmniejszą). W rezultacie najczęściej otrzymamy jedno, dwu lub trójcyfrową liczbę, która w zależności od uzyskanej wartości decyduje o precyzji tego selektora (no i nie zawsze prawidłowo odczytywana, ale zaraz to wyjaśnię).

Jeśli dalej nie rozumiesz określania precyzji reguł CSS, przejdźmy do praktyki, gdzie na podstawie książki Charles'a Wyke-Smith'a pt. „CSS. Witryny internetowe szyte na miarę. Autorytety informatyki. Wydanie III” przedstawię kilka przykładowych selektorów wraz z odpowiadającymi im wartościami I–C–E. W poniższej tabeli przedstawiłem 6 różnych selektorów, zaczynając od najmniej precyzyjnego, aż do tego o najwyższej precyzji:

Precyzja
I–C–E
Nieprawidłowo
odczytana precyzja
Selektor
0–0–11p
0–1–111p.largetext
1–0–1101p#largetext
1–0–2102body p#largetext
1–1–3113body p#largetext ul.mylist
1–1–4114body p#largetext ul.mylist li

Jak sam widzisz, selektor w postaci pojedynczego znacznika jest najmniej ważny (jednocyfrowa liczba). Jednak jeśli dodamy do niego nazwę klasy, staje się bardziej istotny dla arkusza CSS (dwucyfrowa liczba). Natomiast wybranie go za pomocą identyfikatora ma największe znaczenie w określeniu precyzji selektora (tutaj mamy do czynienia z trójcyfrowymi liczbami). Ponadto, warto zwrócić uwagę na jeszcze jedną rzecz. Na przykład w selektorze body p#largetext mamy 1 identyfikator, 0 klas oraz 2 elementy, stąd ta liczba precyzji – 102. Natomiast w selektorze body p#largetext ul.mylist li mamy 1 identyfikator, 1 klasę oraz aż 4 zwykłe elementy (znaczniki), co daje liczbę precyzji 114. Drugi wspomniany selektor ma większą wartość precyzji niż pierwszy (102 < 114), dlatego zostanie zastosowany w pierwszej kolejności, bez względu na to w jakiej kolejności znajduje się w arkuszu CSS.

Jednak jak wyjaśnia Eric Meyer w książce „Podręcznik CSS. Eric Meyer o tworzeniu nowoczesnych układów stron WWW. Smashing Magazine” wszystkie „poziomy” precyzji I–C–E (czyli poszczególne wartości oddzielane za pomocą seperatorów w postaci myślników) są jakby samodzielnymi wartościami. Z tego względu przykładowo selektor składający się z jednego selektora klasy ma większą precyzję niż składający się z 13 selektorów elementów, tak jak przedstawiłem poniżej:

Precyzja
I–C–E
Nieprawidłowo
odczytana precyzja
Selektor
0–1–010.aside
0–0–1313div table tbody tr td div ul li ol li ul li pre

Gdyby nie stosowano seperatorów wartości I–C–E, otrzymalibyśmy wartości 10 i 13 i wyciągnęlibyśmy błędny wniosek, że drugi selektor jest precyzyjnieszy od pierwszego. Zanim wprowadzono zapis z separatorami, we wcześniejszych wersjach CSS było z tym mnóstwo problemów.

Podsumowując – w pierwszej kolejności jest stosowany selektor o najwyższej precyzji, następnie stopniowo coraz to niższej, aż na końcu selektor o najniższej precyzji.

Wykorzystanie i potencjalne pułapki

Pewnie teraz zadasz pytanie po co utrudniać sobie życie z nadawaniem różnych precyzji każdemu selektorowi, skoro można każdemu nadać identyfikator o identycnzej precyzji i bez jakiejkolwiek kolizji odwołać się do niego w arkuszu CSS? Otóż odpowiedź jest bardzo prosta: klas, a w szczególności identyfikatorów należy używać jak najrzadziej. Ich głównym przeznaczeniem jest nadawanie określonych stanów elementom na stronie lub odwoływanie się do nich z poziomu języka JavaScript. Jednak w niektórych przypadkach można uniknąć sporo poważnych problemów poprzez odejście od tejże zasady i wymusić zastosowanie pożądanej reguły CSS wykorzystując identyfikatory. Wiem to z własnego doświadczenia i jeśli jesteś ciekaw z jakimi kłopotami możesz się spotkać – czytaj jako przykład opisany poniżej mój niedziałający kod, a następnie zapoznaj się z rozwiązaniem zaistniałego problemu.

Tak więc, moim problemem było odpowiednie wyświetlanie lub ukrywanie głównych elementów nawigacyjnych responsywnej strony internetowej w zależności od rozdzielczości ekranu urządzenia na jakim jest oglądana. Domyślnie (na smartfonach i tabletach) wszystkie odsyłacze miały być ukryte, oprócz pierwszego (który stanowił logo serwisu oraz przejście do strony głównej) i ostatniego (ikona hamburgera służąca do rozwijania menu). W przypadku otwarcia strony na ekranie o dużej szerokości wszystkie wspomniane odsyłacze powinny być widoczne, lecz oprócz ostatniego (ponieważ gdy widać wszystkie linki nie ma potrzeby wyświetlać ikony rozwijania menu). Do osiągnięcia tego celu napisałem poniższy kod:

header > nav > a:not(:first-child):not(:last-child) {
	display: none;
}
@media screen and (min-width: 900px) {
	header > nav > a:not(:last-child) {
		display: block;
	}
}

Mogłoby się wydawać, że reguła z zapytaniem medialnym zostanie poprawnie zintrepetowana, gdyż w kolejności znajduje się na drugim miejscu, lecz niestety nie będzie działała prawidłowo ze względu na wyższą precyzję pierwszej reguły, która przesłoni regułę drugą. W pierwszej regule występują 3 elementy (znaczniki header, nava), 2 klasy (a właściwie pseudoklasy :first-child:last-child). Natomiast w drugiej regule (w zapytaniu medialnym) również występują 3 elementy (znaczniki header, nava), lecz tylko 1 klasa (pseudoklasa :last-child). Sumarycznie, precyzja perwszej reguły wynosi 23, drugiej zaś 13. Istotne jest, że w obu regułach nie występują jakiekolwiek identyfikatory, a selektory :not nie są brane pod uwagę.

Co począć w takiej sytuacji? Otóż w regule, która ma być bardziej precyzyjna, w drodze wyjątku można użyć identyfikatora. Nie jest to najlepsze rozwiązanie bo jak sam wcześniej stwierdziłem należy unikać takich praktyk. Podkreślam – jest to ostatnia deska ratunku, lecz czasami jedyna! Mówię szczególnie o dużych projektach ze skomplikowanymi regułami CSS. Ale do rzeczy – spójrz teraz na poniższy kod:

header > nav > a:not(:first-child):not(:last-child) {
	display: none;
}
@media screen and (min-width: 900px) {
	header > nav > a:not(#menu-icon) {
		display: block;
	}
}

W powyższym kodzie w drugiej regule zamieniłem pseudoklasę :last-child na identyfikator #menu-icon. Jako, że pojedynczy identyfikator dodaje do wartości precyzji aż 100, dlatego nie ma jaj żeby nie przesłonił tej pierwszej reguły. W poprzednim przykładzie (na listingu z tym niepoprawnym kodem) precyzja drugiej tej reguły jak pamiętamy wynosiła 13. Natomiast w drugim przykładzie (na listingu z tym prawidłowym kodem) po wprowadzeniu identyfikatora precyzja tej reguły wzrosła aż do 103! Od tego momentu wszystkie poszczególne elementy nawigacyjne będą prawidłowo wyświetlane w zależności od rozdzielczości ekranu.

Zakończenie

Mam nadzieję, że ogarnąłeś wszystko co napisałem i bez problemu będziesz w stanie poradzić sobie z przesłanianiem reguł CSS oraz wyznaczaniem kolejności ich stosowania według obliczanej precyzji. Zapewne przyznasz, że komputery prawidłowo interpretują kod, lecz nie są łaskawe dla mniej doświadczonych programistów. Jak się dowiedziałeś, sam miałem z tym problem, ale zawsze jest jakieś rozwiązanie, które Ci przedstawiłem abyś uniknął w przyszłości podobnych problemów. Dziękuję za uwagę i proszę o komentarz! 🙂

Komentarze

Ten wpis nie ma jeszcze żadnych komentarzy. 😟

Dodaj własny komentarz

Formularz dodawania komentarza
Kontynuując przeglądanie niniejszej witryny internetowej automatycznie wyrażasz zgodę na wykorzystanie plików cookies.