Kaskadowość w CSS – wszystko, co musisz wiedzieć

0
23

Kaskadowość w CSS to algorytm przeglądarki decydujący, która reguła stylów zostanie zastosowana do elementu HTML, gdy wiele deklaracji próbuje go zmodyfikować. Mechanizm ten ocenia konflikty w oparciu o pochodzenie arkusza, wagę selektora oraz ostatecznie fizyczną kolejność w kodzie źródłowym.

Większość frontendowców traktuje ten system po macoszemu. Zrobiliśmy na wdrożeniu panelu dla małej hurtowni przy ulicy Ząbkowskiej na warszawskiej Pradze prosty refaktor komponentów. Potem zawiesiło się renderowanie koszyka, więc wprowadzono poprawkę na szybko przed poniedziałkiem. (Siedziałem nad tym kodem do trzeciej nad ranem, pijąc zimną kawę i klnąc pod nosem na kogoś, kto w 2019 roku wrzucił w główny plik stylów globalne nadpisywanie marginesów). System głupiał. Przeglądarka czytała stare reguły z wtyczki, a moje nowe klasy były ignorowane. To jest właśnie ten moment. Brak zrozumienia, jak przeglądarka układa priorytety, kończy się dopisywaniem kolejnych łatek. Tworzymy kodowe potworki.

Dlaczego przeglądarka ignoruje moje style CSS?

To najczęstsze pytanie na forach dla programistów. Maszyna ma ścisły zestaw reguł. Najpierw patrzy na pochodzenie stylu.

Istnieją style domyślne przeglądarki, style użytkownika i style autora. My jako twórcy stron kontrolujemy te ostatnie. Przeglądarka zawsze daje pierwszeństwo stylom autora. Ale tu zaczynają się schody. Wewnątrz twojego kodu CSS dochodzi do starcia. Algorytm wchodzi w fazę liczenia wagi selektorów. Zwykły tag HTML przegrywa z klasą. Klasa przegrywa z identyfikatorem. I to wszystko ma sens, dopóki nie zaczniesz pisać tasiemcowych selektorów celujących w jeden przycisk. Przeglądarka po prostu liczy matematycznie, która ścieżka ma wyższą wartość punktową. Mniejsza wartość odpada.

Jak działa waga selektorów w praktyce?

Waga selektora to system punktowy. Każdy typ selektora ma przypisaną określoną wartość. Algorytm sumuje te punkty i wyłania zwycięzcę.

  • Selektory identyfikatorów (ID) dają najwięcej punktów. Jeżeli użyjesz #naglowek w swoim pliku, to nadpiszesz niemal każdą inną regułę opartą na klasach, co często prowadzi do sytuacji, w której programiści nie potrafią później zmienić koloru tekstu w mniejszym komponencie, bo główny plik trzyma element w żelaznym uścisku punktacji i nie pozwala na jakiekolwiek modyfikacje z poziomu pobocznego arkusza dla konkretnej podstrony.
  • Selektory klas dają wartość średnią.

To jest bez mała najgorsza opcja z wszystkich. Używanie ID do stylizowania elementów. Zawsze kończy się płaczem. Zablokujesz sobie możliwość łatwego nadpisywania komponentów. Zmieniliśmy te zasady na robocie w zeszłym miesiącu. Zespół ma zakaz używania ID w CSS. Używamy tylko klas. Kropka.

Co robi !important i dlaczego psuje kod?

Deklaracja !important to brutalne przerwanie naturalnego biegu algorytmu. Wymusza ona zastosowanie konkretnej reguły. Ignoruje specyficzność. Ignoruje kolejność. To ostateczny argument siłowy.

Większość początkujących wkleja ten modyfikator gdzie popadnie. Widzą, że styl nie działa, więc dopisują wykrzyknik. Kod zaczyna puchnąć. Utrzymanie tego staje się koszmarem. Używasz jednego wymuszenia, żeby naprawić błąd. Za tydzień musisz użyć kolejnego, żeby nadpisać ten pierwszy. Architektura się sypie. Silnik renderujący traci swój przewidywalny rytm. Zamiast płynnie kaskadować style z góry na dół, zderza się z blokadami rozsianymi losowo po całym projekcie.

Kiedy użycie !important ma sens?

Zastanawiacie się zresztą, dlaczego to słowo w ogóle powstało? Sam się nad tym borykałem dzisiaj u siebie we wtorek. Wbrew pozorom ma ono konkretne, uzasadnione zastosowania. Podamy wam dwa przypadki.

  • Nadpisywanie stylów wstrzykiwanych bezpośrednio w kod HTML przez zewnętrzne skrypty reklamowe lub widgety czatów, gdzie nie mamy absolutnie żadnego dostępu do kodu źródłowego wtyczki i musimy siłowo ukryć wyskakujący baner psujący układ strony na urządzeniach mobilnych, żeby użytkownik mógł w ogóle przeczytać tekst.
  • Tworzenie klas narzędziowych w metodykach takich jak Tailwind.

I to tyle. Żadnych innych wymówek.

Czym różni się dziedziczenie od kaskadowości?

Ludzie nagminnie mylą te dwa pojęcia. Dziedziczenie to proces przekazywania pewnych właściwości z elementu nadrzędnego na elementy podrzędne. Kaskadowość w CSS rozstrzyga konflikty między regułami celującymi bezpośrednio w ten sam element.

Jeżeli ustawisz kolor tekstu na ciele dokumentu (tag body), akapity wewnątrz przyjmą ten kolor. To jest dziedziczenie. Ale jeśli na konkretny akapit nałożysz osobną klasę zmieniającą kolor, zadziała kaskadowość. Mechanizm oceni, że klasa ma wyższą wagę niż dziedziczona reguła z tagu body. Proste. Zrozumienie tej granicy poprawia architekturę całego projektu. Nie musisz pisać osobnych deklaracji dla każdego elementu. Dobrym pomysłem jest poleganie na dziedziczeniu typografii od samej góry drzewa DOM. Mniej kodu do utrzymania.

Jak drzewo DOM wpływa na kaskadowość w CSS?

Struktura dokumentu HTML buduje szkielet. Drzewo DOM to reprezentacja tego szkieletu w pamięci przeglądarki. Kaskadowość opiera się na tym szkielecie. Reguły CSS są aplikowane do węzłów DOM. Jeżeli węzeł ma kilka pasujących deklaracji, silnik CSSOM (CSS Object Model) wkracza do akcji.

CSSOM łączy się z DOM. Tworzą wspólnie drzewo renderowania. W tym procesie maszyna odrzuca węzły niewidoczne, takie jak sekcja head czy elementy z właściwością display ustawioną na none. Konflikty stylów są rozwiązywane zanim cokolwiek pojawi się na ekranie monitora. Prawda jest zresztą absolutnie taka, że im głębiej zagnieżdżony element DOM, tym trudniej śledzić ścieżkę kaskady. Piszemy płaskie struktury HTML, żeby ułatwić pracę algorytmom. Zbyt głębokie drzewo zmusza przeglądarkę do ciężkiej pracy obliczeniowej.

Jak warstwy kaskadowe @layer zmieniają pisanie frontendu?

To nowość w specyfikacji W3C. Warstwy kaskadowe pozwalają na grupowanie reguł i jawne ustalanie ich priorytetów. Zmienia to układ sił. Wcześniej walczyliśmy na punkty specyficzności. Teraz możemy zdefiniować, że warstwa z resetem stylów ma zawsze niższy priorytet niż warstwa z komponentami bazowymi.

Dzięki @layer kolejność deklaracji warstw na początku pliku definiuje ich ważność. Nawet jeśli selektor w warstwie niższej ma ogromną wagę, przegra z prostym selektorem tagu umieszczonym w warstwie o wyższym priorytecie. Wprowadziliśmy nową funkcję u nas w głównym projekcie. Działa to obłędnie. Likwiduje mankament wyścigu zbrojeń na selektory. Definiujesz warstwy raz na samej górze pliku. Potem po prostu wrzucasz kod do odpowiedniego bloku @layer i nie martwisz się, że jakaś losowa klasa z wtyczki zniszczy twój układ formularza.

Czy kolejność plików CSS ma znaczenie?

Zdecydowanie tak. Gdy specyficzność i pochodzenie są identyczne, przeglądarka bierze pod uwagę kolejność parsowania. Reguła wczytana później nadpisuje tę wczytaną wcześniej. Ostatni na liście wygrywa.

Wielu deweloperów o tym zapomina. Dołączają pliki w losowej kolejności w nagłówku HTML. Potem dziwią się, że ich własne style są nadpisywane przez bibliotekę zewnętrzną. Ładowanie stylów bazowych musi odbywać się przed stylami specyficznymi dla danej podstrony. Inaczej walczysz z wiatrakami. CZYTAJ kod od góry do dołu. Maszyna robi dokładnie to samo.

Zderzenie stylów inline z zewnętrznym arkuszem

Atrybut style dodany bezpośrednio do tagu HTML ma morderczą siłę. Omija on niemal całą standardową kaskadowość w CSS. Ma wyższą wagę niż identyfikatory i klasy. Przegrywa tylko z deklaracją wymuszającą.

Używanie stylów inline w nowoczesnych aplikacjach to proszenie się o kłopoty. Kod HTML staje się nieczytelny. Zmiana wyglądu wymaga edytowania struktury dokumentu. Łamiemy w ten sposób zasadę oddzielenia treści od prezentacji. Widziałem projekty, gdzie cały system siatki opierał się na wstrzykiwanych z JavaScriptu stylach inline. Działało to przeraźliwie wolno na starszych telefonach. Utrzymanie tego kodu zajmowało zespołowi tygodnie zamiast godzin. Atrybut style powinien być zarezerwowany wyłącznie dla dynamicznych wartości obliczanych w czasie rzeczywistym w skryptach JS, takich jak pozycja kursora myszy na ekranie.

Zobrazujemy to prostym zestawieniem punktacji dla podstawowych typów selektorów. To twarde dane z silników przeglądarek.

Tag HTML i pseudoelementy Waga niska (1 punkt)
Klasy, atrybuty i pseudoklasy Waga średnia (10 punktów)
Identyfikatory (ID) Waga wysoka (100 punktów)
Style wpisane w atrybut style (inline) Waga najwyższa (1000 punktów)

To u nas w sumie chyba zasada z przedwczoraj, bo pewności do tych zmian w specyfikacji nikt obecnie nie ma o 2025 r. Zespół pracujący nad silnikiem Chromium ciągle coś zmienia pod maską. Ale na ten moment tak to liczą wszystkie wiodące programy do przeglądania sieci.

Debugowanie kaskadowości w narzędziach deweloperskich

Bez narzędzi wbudowanych w przeglądarkę jesteś ślepy. DevTools to podstawa pracy. W zakładce Elements widzisz dokładnie drzewo DOM. Po prawej stronie masz panel ze stylami. Tam dzieje się cała magia.

Przekreślone właściwości oznaczają, że przegrały one walkę w kaskadzie. Ktoś inny miał wyższą wagę selektora. Narzędzie pokazuje dokładnie plik i numer linijki, w której znajduje się zwycięska reguła CSS. Klikasz i od razu wiesz, gdzie poprawić kod. Zamiast zgadywać w edytorze tekstu, patrzysz na twardy wynik pracy algorytmu. Dobrym pomysłem jest filtrowanie zakładki stylów po konkretnej właściwości. Wpisujesz margin i widzisz wszystkie reguły modyfikujące margines dla wybranego węzła. Od razu łapiesz intruza.

Co się dzieje w izolowanym Shadow DOM?

Web Components wprowadzają pojęcie Shadow DOM. To ukryte, odizolowane drzewo elementów. Kaskadowość w CSS zachowuje się tutaj inaczej. Style zdefiniowane wewnątrz Shadow DOM nie wyciekają na zewnątrz. Style z zewnątrz zazwyczaj nie przenikają do środka.

To daje izolację. Zbudowaliśmy na tym system powiadomień. Nawet jeśli na głównej stronie ktoś zdefiniował wykrzyknik dla wszystkich paragrafów, nasz komponent powiadomienia w Shadow DOM pozostał nienaruszony. Wyglądał dokładnie tak, jak go zaprojektowaliśmy. Mechanizm ten tworzy twardą granicę. Oczywiście istnieją wyjątki, takie jak dziedziczenie zmiennych CSS, które przenikają przez tę barierę. Zmienne to osobny temat. Przekazują one wartości zgodnie z drzewem DOM, a kaskadowość decyduje o tym, która wartość zmiennej zostanie ostatecznie przypisana do konkretnego elementu.

Jak przeglądarka czyta style użytkownika?

Każda przeglądarka ma wbudowany arkusz stylów. Nazywamy go arkuszem User Agent. To on nadaje domyślny wygląd elementom HTML. Pogrubia tagi h1. Dodaje marginesy do akapitów. Nadaje niebieski kolor linkom.

Kaskadowość w CSS umieszcza te reguły na samym dole hierarchii ważności. Dowolny styl napisany przez ciebie nadpisze styl domyślny przeglądarki. Dlatego stosujemy pliki typu reset.css. Chcemy wyrównać różnice między Chrome, Firefoxem i Safari. Zerujemy domyślne marginesy. Wymuszamy spójne zachowanie box-sizing. Zdejmujemy fabryczne obramowania z przycisków. Bez tego kroku nasz kod budowany jest na niestabilnym fundamencie, który różni się w zależności od sprzętu.

Najczęściej zadawane pytania (FAQ)

  • Co to jest kaskadowość w CSS?
    To algorytm przeglądarki rozstrzygający konflikty między różnymi regułami stylów celującymi w ten sam element HTML.
  • Dlaczego przeglądarka przekreśla moje style w DevTools?
    Inna reguła CSS ma wyższą wagę selektora lub znajduje się niżej w kodzie źródłowym.
  • Jak obliczyć specyficzność selektora?
    Identyfikatory mają największą wartość. Klasy, pseudoklasy i atrybuty mają wartość średnią. Tagi HTML mają najniższą punktację. Sumujesz punkty dla każdego elementu w selektorze.
  • Czy !important to dobra praktyka?
    Zdecydowanie nie. Używaj tego tylko do nadpisywania stylów z obcych skryptów.
  • Co robi dyrektywa @layer?
    Pozwala na zgrupowanie stylów w warstwy i jawne ustalenie ich priorytetów, co uniezależnia ważność reguł od wagi pojedynczych selektorów.
  • Czym się różni dziedziczenie od kaskadowości?
    Dziedziczenie przekazuje właściwości z rodzica na dziecko. Kaskadowość decyduje, która z konkurujących reguł zostanie zastosowana do jednego konkretnego elementu.

Otwórz swój najstarszy projekt poboczny na dysku twardym. Poszukaj w kodzie źródłowym wykrzykników wymuszających style. Spróbuj usunąć chociaż połowę z nich, poprawiając po prostu architekturę selektorów według wagi i rozbijając tasiemcowe klasy. Zobaczysz, jak szybko twój główny plik ze stylami zacznie współpracować z przeglądarką, zamiast z nią bezsensownie walczyć o każdy piksel na ekranie monitora.

Bibliografia:
1. MDN Web Docs – https://developer.mozilla.org
2. W3C (World Wide Web Consortium) – https://www.w3.org
3. CSS-Tricks – https://css-tricks.com
4. Web.dev – https://web.dev