Ogranicz zasięg selektorów za pomocą reguły @scope w CSS

Dowiedz się, jak używać zasięgu @, aby wybierać elementy tylko w ograniczonym poddrzewie DOM.

Browser Support

  • Chrome: 118.
  • Edge: 118.
  • Firefox: behind a flag.
  • Safari: 17.4.

Source

Delikatna sztuka tworzenia selektorów arkusza CSS

Podczas pisania selektorów możesz czuć się rozdarta między 2 światami. Z jednej strony musisz dokładnie określić, które elementy chcesz wybrać. Z drugiej strony chcesz, aby selektory były łatwe do zastąpienia i nie były ściśle powiązane ze strukturą DOM.

Jeśli na przykład chcesz wybrać „obraz nagłówka w obszarze treści komponentu karty”, co jest dość specyficznym wyborem elementu, prawdopodobnie nie chcesz pisać selektora takiego jak .card > .content > img.hero.

Nie chcesz jednak wpisywać jako selektora tego elementu tylko img, ponieważ spowodowałoby to wybranie wszystkich elementów obrazu na stronie.

Znalezienie odpowiedniej równowagi jest często dość trudne. W ciągu lat niektórzy deweloperzy opracowali rozwiązania i metody obejścia problemów, które mogą Ci pomóc w takich sytuacjach. Na przykład:

  • Metodologie takie jak BEM wymagają, aby ten element miał klasę card__img card__img--hero, co pozwala zachować niską specyficzność, a jednocześnie pozwala na dokładne określenie tego, co wybierasz.
  • Rozwiązania oparte na JavaScript, takie jak CSS ograniczony czy składniki stylizowane, przepisują wszystkie selektory, dodając do nich losowo wygenerowane ciągi znaków, np. sc-596d7e0e-4, aby zapobiec ich kierowaniu na elementy po drugiej stronie strony.
  • Niektóre biblioteki całkowicie rezygnują z selektorów i wymagają umieszczania elementów sterujących stylami bezpośrednio w tagach.

Co jednak, jeśli nie potrzebujesz żadnego z tych elementów? Co, jeśli CSS dałoby Ci możliwość precyzyjnego wybierania elementów bez konieczności pisania selektorów o wysokiej specyficzności lub takich, które są ściśle powiązane z Twoim DOM-em? Właśnie w tym przypadku przydaje się funkcja @scope, która umożliwia wybieranie elementów tylko w poddrzewie DOM.

Przedstawiamy @scope

Za pomocą @scope możesz ograniczyć zasięg selektorów. Aby to zrobić, ustaw korzeń zakresu, który określa górną granicę poddrzewa, na które chcesz kierować reklamy. Gdy ustawisz ograniczający element główny, zawarte w nim reguły stylów (nazywane ograniczonymi regułami stylów) mogą wybierać tylko z ograniczonego poddrzewa DOM.

Jeśli na przykład chcesz kierować się tylko na elementy <img> w komponencie .card, ustawiasz .card jako element nadrzędny reguły at-rule @scope.

@scope (.card) {     img {         border-color: green;     } } 

Reguła stylu ograniczonego zakresem img { … } może skutecznie wybierać tylko elementy <img>, które są w zakresie dopasowanego elementu .card.

Aby zapobiec zaznaczaniu elementów <img> w obszarze treści karty (.card__content), możesz bardziej sprecyzować selektor img. Innym sposobem jest wykorzystanie faktu, że reguła @scope przyjmuje też ograniczenie zakresu, które określa dolną granicę.

@scope (.card) to (.card__content) {     img {         border-color: green;     } } 

Ta ograniczona reguła stylu dotyczy tylko elementów <img>, które w drzewie przodków znajdują się między elementami .card.card__content. Ten typ zakresu z górną i dolną granicą często nazywany jest zakresem ciastka z lukrem.

Selektor :scope

Domyślnie wszystkie reguły stylu z ograniczeniami są względne do katalogu źródeł. Możesz też kierować reklamy na sam element skojarzony z elementem głównym. Użyj do tego selektora :scope.

@scope (.card) {     :scope {         /* Selects the matched .card itself */     }     img {        /* Selects img elements that are a child of .card */     } } 

Selektory w ramach reguł stylów ograniczonych są domyślnie poprzedzane przez :scope. Jeśli chcesz, możesz wyraźnie to zaznaczyć, dodając przedrostek :scope. Możesz też użyć selektora &zagnieżdżania CSS.

@scope (.card) {     img {        /* Selects img elements that are a child of .card */     }     :scope img {         /* Also selects img elements that are a child of .card */     }     & img {         /* Also selects img elements that are a child of .card */     } } 

Limit zakresu może używać pseudoklasy :scope, aby wymagać określonego związku z korzeniami zakresu:

/* .content is only a limit when it is a direct child of the :scope */ @scope (.media-object) to (:scope > .content) { ... } 

Limit zakresu może się też odwoływać do elementów spoza swojego elementu skojarzonego za pomocą elementu :scope. Na przykład:

/* .content is only a limit when the :scope is inside .sidebar */ @scope (.media-object) to (.sidebar :scope .content) { ... } 

Pamiętaj, że reguły stylów ograniczonych nie mogą wykraczać poza poddrzewo. Wybrane elementy, takie jak :scope + p, są nieprawidłowe, ponieważ próbują wybrać elementy, które nie są objęte zakresem.

@scope i specyficzność

Selektory użyte w preludium do @scope nie wpływają na specyficzność zawartych selektorów. W przykładzie poniżej specyficzność selektora img nadal wynosi (0,0,1).

@scope (#sidebar) {     img { /* Specificity = (0,0,1) */              } } 

Specyficzność :scope jest taka jak w przypadku zwykłej pseudoklasy, czyli (0,1,0).

@scope (#sidebar) {     :scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */              } } 

W tym przykładzie wewnętrznie selektor & jest zastępowany przez selektor używany do określania zakresu, który jest otoczony selektorem :is(). W efekcie przeglądarka użyje selektora :is(#sidebar, .card) img do dopasowania. Ten proces nazywa się usuwaniem cukru.

@scope (#sidebar, .card) {     & img { /* desugars to `:is(#sidebar, .card) img` */              } } 

Ponieważ & jest odsłodzony za pomocą :is(), specyficzność & jest obliczana zgodnie z zasadami specyficzności :is(): specyficzność & jest taka jak najbardziej szczegółowego argumentu.

W tym przykładzie szczegółowość argumentu :is(#sidebar, .card) jest taka sama jak szczegółowość najbardziej szczegółowego argumentu, czyli #sidebar, i w konsekwencji staje się (1,0,0). Połącz to ze specyfiką img, która wynosi (0,0,1), a w efekcie specyfika dla całego złożonego selektora to (1,0,1).

@scope (#sidebar, .card) {     & img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */              } } 

Różnica między :scope& w wersji @scope

Oprócz różnic w sposobie obliczania specyficzności :scope& różnią się tym, że :scope reprezentuje dopasowany korzeń zakresu, a & – selektor użyty do dopasowania do korzenia zakresu.

Z tego powodu można użyć & kilka razy. W przeciwieństwie do :scope, którego można użyć tylko raz, ponieważ nie można dopasować korzenia zakresu w korzenia zakresu.

@scope (.card) {   & & { /* Selects a `.card` in the matched root .card */   }   :scope :scope { /* ❌ Does not work */        } } 

Zakres bez wstępu

Podczas pisania stylów wbudowanych za pomocą elementu <style> możesz ograniczyć zakres reguł stylów do elementu nadrzędnego elementu <style>, nie określając żadnego korzenia zakresu. Aby to zrobić, pomiń wstęp @scope.

<div class="card">   <div class="card__header">     <style>       @scope {         img {           border-color: green;         }       }     </style>     <h1>Card Title</h1>     <img src="…" height="32" class="hero">   </div>   <div class="card__content">     <p><img src="…" height="32"></p>   </div> </div> 

W przykładzie powyżej reguły ograniczone dotyczą tylko elementów w elementach div o nazwie klasy card__header, ponieważ element div jest elementem nadrzędnym elementu <style>.

@scope w kaskadzie

W kaskadzie CSS @scope dodaje też nowe kryterium: bliskość zakresu. Ten krok pojawia się po specyficzności, ale przed kolejnością występowania.

Wizualizacja kaskady CSS

Zgodnie ze specyfikacją:

Porównując deklaracje, które pojawiają się w regułach stylów z różnymi korzeniami zakresu, wygrywa deklaracja z najmniejszą liczbą skoków między korzeniami generacji lub elementami siostrzanymi a podmiotem reguły stylu z zakresem.

Ten nowy krok jest przydatny podczas zagnieżdżania kilku wariantów komponentu. Weź pod uwagę ten przykład, w którym nie użyto jeszcze znacznika @scope:

<style>     .light { background: #ccc; }     .dark  { background: #333; }     .light a { color: black; }     .dark a { color: white; } </style> <div class="light">     <p><a href="#">What color am I?</a></p>     <div class="dark">         <p><a href="#">What about me?</a></p>         <div class="light">             <p><a href="#">Am I the same as the first?</a></p>         </div>     </div> </div> 

Podczas wyświetlania tego fragmentu znacznika trzeci link będzie miał wartość white zamiast black, mimo że jest elementem podrzędnym elementu div z zastosowanej klasą .light. Wynika to z kryterium kolejności wyświetlania, którego używa kaskada do określenia zwycięzcy. Widzi, że .dark a zostało zadeklarowane jako ostatnie, więc wygra na podstawie reguły .light a

Dzięki kryterium zbliżenia do zakresu problem został rozwiązany:

@scope (.light) {     :scope { background: #ccc; }     a { color: black;} }  @scope (.dark) {     :scope { background: #333; }     a { color: white; } } 

Ponieważ oba ograniczone selektory a mają tę samą specyficzność, w działanie wchodzi kryterium ograniczania zasięgu. Oblicza wagę obu selektorów na podstawie ich bliskości do korzenia zakresu. W przypadku tego trzeciego elementu a do głównego elementu .light jest tylko 1 skoczek, a do elementu .dark – 2 skoczki. Dlatego zwycięży selektor a w elementach .light.

Uwaga końcowa: izolacja selektora, a nie izolacja stylu

Pamiętaj, że @scope ogranicza zasięg selektorów, ale nie zapewnia izolacji stylów. Właściwości, które są dziedziczone przez elementy podrzędne, będą nadal dziedziczone, nawet jeśli wartość @scope jest większa niż dolna granica. Jedną z takich usług jest color. Gdy zadeklarujesz to w zakresie donut, color będzie nadal dziedziczyć wartości od elementów wewnątrz otworu donut.

@scope (.card) to (.card__content) {   :scope {     color: hotpink;   } } 

W powyższym przykładzie element .card__content i jego elementy podrzędne mają kolor hotpink, ponieważ dziedziczą tę wartość z elementu .card.

(Zdjęcie na okładce: rustam burkhanov na Unsplash)