Dowiedz się, jak używać zakresu @, aby wybierać elementy tylko w ograniczonym poddrzewie DOM.
Delikatna sztuka pisania selektorów CSS
Pisząc selektory, możemy się rozdzielić 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 główny w obszarze treści komponentu karty”, co jest dość specyficznym wyborem elementu, prawdopodobnie nie chcesz pisać selektora takiego jak .card > .content > img.hero
.
- Ten selektor ma dość wysoką specyficzność
(0,3,1)
, co utrudnia jego zastąpienie w miarę rozrastania się kodu. - Dzięki bezpośredniemu kombinatorowi podrzędnemu jest on ściśle powiązany ze strukturą DOM. Jeśli znaczniki ulegną zmianie, musisz też zmienić plik CSS.
Nie chcesz jednak wpisywać jako selektora tego elementu tylko img
, ponieważ spowodowałoby to wybranie wszystkich elementów obrazu na stronie.
Znalezienie w tym kontekście odpowiedniej równowagi jest często sporym wyzwaniem. Na przestrzeni lat niektórzy programiści opracowali rozwiązania i sposoby obejścia tego problemu, które miały 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 umożliwia 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 generowane 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 stylów bezpośrednio w tagach.
A co, jeśli ich nie potrzebujesz? 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. W tym celu ustaw poziom główny zakresu, który określa górną granicę drzewa podrzędnego, na które chcesz kierować reklamy. Gdy ustawisz ograniczający element skojarzony, zawarte w nim reguły stylów (nazywane ograniczonymi regułami stylów) mogą wybierać tylko z tego ograniczonego poddrzewa DOM.
Aby np. kierować reklamy tylko na elementy <img>
w komponencie .card
, ustaw .card
jako pierwiastek zakresu zakresu reguły @scope
.
@scope (.card) {
img {
border-color: green;
}
}
Reguła stylu o zakresie img { … }
może w praktyce wybrać tylko te 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
i .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 o zakresie są określone względem poziomu głównego zakresu. Możesz też kierować reklamy na sam element skojarzony z korzeniami. 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 w sposób wyraźny dodać :scope
samodzielnie. Możesz też dołączyć selektor &
na początku listy z 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 swego 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 poniższym przykładzie wewnętrznie pole &
jest przepisywane do selektora służącego do określania poziomu głównego, opakowanego wewnątrz selektora :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
a &
w: @scope
Oprócz różnic w sposobie obliczania specyficzności :scope
i &
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: zakres zbliżenia. Ten krok następuje po doprecyzowaniu, ale przed kolejnością występowania.
Podczas porównywania deklaracji, 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 przydaje się 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 znaczników trzeci link będzie miał wartość white
zamiast black
, mimo że jest elementem podrzędnym elementu div
z zastosowanej klasą .light
. Wynika to z kolejności kryterium wyglądu, z której korzysta kaskada w celu wyłonienia 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 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 korzenia zakresu .light
jest tylko 1 przeskok, a do elementu .dark
– 2 przeskoki. W związku z tym selektor a
w tabeli .light
wygra.
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 dla 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)