Pseudo-klasy i pseudo-elementy w CSS, czyli jak uniknąć pułapki!

Ucząc się CSS dosyć szybko poznajemy selektor :hover, być może nieco później ::before i ::after, ale czy wiesz, że ten pierwszy jest pseudo-klasą, a pozostałe dwa to pseudo-elementy? Nie całkiem? W takim razie należysz do licznego grona, bo nawet doświadczeni deweloperzy i deweloperki mylą te pojęcia. A ponieważ tak łatwo o pomyłkę, pytania o pseudo-klasy i pseudo-elementy często pojawiają się podczas frontendowych rozmów kwalifikacjnych.

Jeżeli nie chcesz już nigdy zastanawiać się co jest pseudo-elementem, a co pseudo-klasą, to ten artykuł jest dla Ciebie. Dowiesz się czym są, co je odróżnia od pozostałych selektorów CSS, jakie podobieństwa i różnice są między nimi, oraz dlaczego tak łatwo je pomylić. Zatem, przejdźmy do konkretów.

Pseudo-klasy i pseudo-elementy kontra reszta selektorów

Zacznijmy od tego, czym zarówno pseudo-klasy, jak i pseudo-elementy różnią się od pozostałych selektorów CSS.

Pozostałe selektory CSS operują na elementach obecnych w drzewie dokumentu. Czyli, mówiąc wprost, obecnych w dokumencie HTML. Pozwalają one na selekcję elementów na podstawie:

  • samej nazwy elementu HTML (p, img, a)
  • atrybutu, np. id lub class, albo konkretny atrybut
/* określone elementy HTML */
p { … }
img { … }
a { … }
/* element HTML z ID: id="some-id" */
#some-id { … }
/* elementy HTML z dodaną klasą CSS: class="some-class" */
.some-class { … }

Powyższe selektory są selektorami prostymi (simple selectors). Proste selektory możemy ze sobą łączyć w łańcuchy, tworząc selektory złożone (compound selectors):

/* elementy <a> zawarte wewnątrz elemetów <p>, które mają klasę "some-class" */
p.some-class a { … }

W selektorach złożonych możemy dodatkowo użyć kombinatorów, by uściślić jak elementy mają się do siebie w strukturze drzewa (>, ~, +)

/* elementy <ul> będące bezpośrednimi dziećmi elemetów <nav>, które mają klasę "main-menu" */
nav.main-menu > ul { … }
/* elementy <p> występującje po elemencie z klasą "break", niekoniecznie bepośrednio po */
.break ~ p { … }
/* elementy <p> występującje bezpośrednio po elemencie <img> */
img + p { … }

We wszystkich tych przypadkach mamy selektory, które wskazują bezpośrednio na elementy drzewa dokumentu. Nawet łącząc je w długie i skomplikowane łańcuchy (co, tak na marginesie, nie jest jest dobrym pomysłem!), wciąż operujemy na nazwach znaczników i ich atrybutach, czyli na rzeczach, które fizycznie istnieją w kodzie HTML strony.

Pseudo-klasy

Spójrzmy teraz na pseudo-klasy. Na przykład tę, o której wspomniałem na początku, czyli :hover. Dodajemy ją do elementu, któremu chcemy nadać pewien styl w momencie gdy znajduje się na nim wskaźnik myszy:

a:hover { … }
.some-class:hover { … }

Zatem mamy tu selektor, który ma zastosowanie w chwili, gdy element ma określony stan. Stan ten nie jest częścią samego dokumentu, lecz efektem interakcji z użytkownikiem.

Weźmy inny przykład, pseudo-klasę :checked, która dotyczy zaznaczonych elementów radio (<input type="radio">), oraz checkbox (<input type="checkbox">):

input[type=checkbox]:checked { … }
.my-checkbox:checked { … }

Tutaj znów mamy selektor, który ma zastosowanie do elementów, które mają określony stan na skutek interakcji z użytkownikiem. I podobnie jak poprzednio, stan ten nie wynika z samego dokumentu.

Czy nie zaczyna się nam wyłaniać pewien wzorzec?

Spójrzmy jeszcze na kilka innych przykładów pseudo-klas:

/* ostatni element z listy */
li:first-child { … }
/* trzeci element z listy */
li:nth-child(3) { … }
/* wszystkie przyciski, które nie są dezaktywowane */
button:not([disabled])

Dwie pierwsze pseudo-klasy dotyczą elementów zlokalizowanych w określonych miejscach listy. Czyli selekcjonujemy elementy w oparciu o pewną cechę dokumentu, ale nie wyrażoną wprost w samym dokumencie. Nie wyrażoną wprost, bo elementy co prawda znajdują w określonej kolejności na liście, ale same nie posiadają cech (na przykład jakichś specjalnych atrybutów) które mówiły jakie w tej kolejności zajmują miejsce.

Ostatni przykład jest interesujący. Mamy tu funkcyjną pseudo-klasę (functional pseudo-class), która dotyczy wszystkich elementów, które nie pasują do selektora w nawiasie (funkcyjną, bo przyjmuje argument, przez co jest podobna do funkcji). Tym razem selekcjonujemy elementy które nie posiadają pewnej cechy. Cecha o której brak nam chodzi, może wynikać z dokumentu lub stanu (zależy jaki selektor umieścimy w nawiasie), ale nie jest wyrażona wprost w dokumencie. Nie wprost w takim sensie, że cechą tych elementów jest to, iż czegoś nie mają.

Myślę, że staje się coraz bardziej jasne, czym charakteryzują się pseudoklasy: pozwalają na selekcję elementów na podstawie ich cech nie wyrażonych wprost w dokumencie (takich jak położenie elementu na liście) oraz cech wynikających z interakcji z użytkownikiem (jak na przykład zaznaczenie, czy focus).

Oto definicja pseudo-klas wzięta wprost ze specyfikacji:

Pseudo-klasy to proste selektory, które umożliwiają selekcję na podstawie informacji, które znajdują się poza drzewem dokumentu lub których wyrażenie przy użyciu innych prostych selektorów może być niewygodne lub niemożliwe. Pseudo-klasy mogą być również dynamiczne, w tym sensie, że element może nabyć lub utracić pseudo-klasę, gdy użytkownik wchodzi w interakcję z dokumentem, bez zmiany samego dokumentu. Pseudoklasy nie pojawiają się w źródle lub drzewie dokumentów ani nie modyfikują ich.

Źródło: https://drafts.csswg.org/selectors/#pseudo-classes

I jak, klarowne? Ta definicja jest dosyć formalna, ale po przejrzeniu wcześniejszych przykładów powinna być zrozumiała.

Pseudo-elementy

Przejdźmy teraz do drugiego tematu tego artykułu, czyli pseudo-elementów. Zacznijmy znów od przykładu wspomnianego na początku, czyli pseudo-elementów ::before i ::after:

<p>jaśnie</p>
<style>
p: {
color: black;
}
p::before {
content: 'prze';
color: red;
}
p::after {
content: 'nie';
color: red;
}
</style>

Rezultatem tego kodu będzie widoczny w dokumencie wyraz “przejaśnienie”, przy czym sylaby “prze” i “nie” będą miały kolor czerwony:

Zrzut ekranu

Pseudo-elementy ::before i ::after powodują wstawienie do dokumentu dodatkowej treści (określonej przez właściwość content). Pamiętaj jednak, że dodana w ten sposób treść nie modyfikuje samego dokumentu i nie powoduje pojawienia się nowego elementu w strukturze drzewa. Jest czysto wizualna i dostępna jedynie z poziomu CSS (nie można na przykład dostać się do niej z JavaScript). Zatem mamy tu przykład pseudo-elementów które pozwalają tworzyć i stylować elementy, które nie istnieją w samym dokumencie. Są to niejako “wirtualne” elementy utworzone przez CSS.

Spójrzmy na inny przykład, pseudo-element :first-letter:

<p>Lorem ipsum dolor sit amet.</p>
<style>
p::first-letter {
font-size: 2em;
}
</style>

Tym razem efektem działania kodu będzie prezentacja pierwszego znaku, czyli litery “L”, czcionką dwukrotnie większą od reszty zdania:

Zrzut ekranu

Zwróć uwagę, że w strukturze dokumentu, litera “L” nie jest samodzielnym elementem, jest po prostu pierwszym znakiem treści paragrafu. To pseudo-element :first-letter pozwala nam traktować tę literę tak, jakby była niezależnym elementem. Czyli znów mamy “wirtualny” element CSS.

I o to właśnie chodzi w pseudo-elementach, pozwalają one z poziomu CSS stylować fragmenty dokumentu nie będące elementami HTML. Tworzą one abstrakcyjne (wirtualne) elementy, nie modyfikując jednocześnie drzewa dokumentu. Elementy te widoczne są jedynie dla CSS.

Podobieństwa i różnice

Omówiliśmy jak działają pseudo-klasy i pseudo-elementy, czas zobaczyć co z tego wynika podsumowując podobieństwa i różnice pomiędzy nimi.

Podobieństwa:

  • Zarówno pseudo-klasy jak i pseudo-elementy reprezentują informacje które nie są bezpośrednio obecne w drzewie dokumentu
  • Nie modyfikują źródła ani drzewa dokumentu

Różnice:

  • Pseudo-klasy odnoszą się do elementów, które istnieją w dokumencie. Odnoszą się też całości elementu, a nie jego części. Dotyczą jego stanu, lub cech wynikających ze struktury dokumentu, i w tym sensie działają na informacjach nie zadeklarowanych w sposób jawny w drzewie dokumentu. Jednak sam element istnieje.
  • Pseudo-elementy, w przeciwieństwie do pseudo-klas, odnoszą się do elementów których faktycznie w dokumencie nie ma. Pseudo-elementy są abstrakcjami istniejącymi jedynie w warstwie CSS. Pozwalają na selekcję fragmentu elementu, lub dodanie nowej treści do istniejącego elementu.
  • Różnią się składnią, choć to różnica subtelna:
    • Selektory pseudo-klas zaczynają się od pojedynczego dwukropka: :class-name.
    • Selektory pseudo-elementów zaczynają się od podwójnego dwukropka: ::element-name.

To o co chodzi z tą pułapką?

Pewnie zastanawiasz się, gdzie w tym wszystkim jest wspomniana w tytule pułapka. Ano właśnie w składni.

Różnice w składni wymagają dodatkowego wyjaśnienia, bo sprawa nie jest taka oczywista. Rzeczywiście w najnowszych specyfikacjach (Selectors Level 3 oraz Selectors Level 4) wygląda to tak, jak napisałem wyżej: nazwy pseudo-klas zaczynają się od :, a nazwy pseudo-elementów od ::. Ale nie zawsze tak było. W starszych wersjach specyfikacji (aż do CSS 2.2) nazwy jednych i drugich zaczynały się od pojedynczego dwukropka: “:”. Czyli pseudo-klasy i pseudo-elementy pod względem składni wyglądały identycznie! To z pewnością jedna z głównych przyczyn nieporozumień, pomyłek, oraz trudności z odróżnieniem pseudo-klas i pseudo-elementów.

Gdyby nie było to wystarczająco pogmatwane, to jeszcze przeglądarki zawsze muszą zachować wsteczną kompatybilność. Dlatego nadal wspierają obie składnie. Przynajmniej w odniesieniu do starszych pseudo-elementów, które były obecnie już w CSS 2.2, bo nowo wprowadzane pseudo-elementy mają już wyłącznie składnię z podwójnym dwukropkiem (::). W materiałach sprzed kilku lat znajdziesz radę, by używać starej składni pseudo-elementów, gdyż ma szersze wsparcie w przeglądarkach. Jednak ten argument mocno się już zdezaktualizował. Obecnie, o ile nie musisz wspierać IE8 (a mam szczerą nadzieję, że nie musisz), nie ma żadnego uzasadnienia dla używania starej składni, zatem zaczynaj pseudo-elementy od podwójnego dwukropka (::).

Tym sposobem dotarliśmy do końca artykułu. Mam nadzieję, że udało mi się rozjaśnić Ci temat podobieństw i różnic pomiędzy pseudo-klasami i pseudo-elementami. Jeżeli podczas technicznego interview natrafisz na dotyczące tego pytanie, z pewnością odpowiesz bez problemu, unikając pułapki.

Ikona Smiley Obserwuj DevSchool!
Logo FacebookLogo Twitter

Copyright 2022 – 2023 ©
Michał Wilkosiński CONIFER MEDIA
Wszystkie prawa zastrzeżone