Leer hoe u @scope kunt gebruiken om alleen elementen binnen een beperkte substructuur van uw DOM te selecteren.
De delicate kunst van het schrijven van CSS-selectors
Bij het schrijven van selectors wordt u misschien heen en weer geslingerd tussen twee werelden. Aan de ene kant wil je behoorlijk specifiek zijn over welke elementen je selecteert. Aan de andere kant wilt u dat uw selectors gemakkelijk te overschrijven zijn en niet nauw gekoppeld zijn aan de DOM-structuur.
Wanneer u bijvoorbeeld “de heldafbeelding in het inhoudsgebied van de kaartcomponent” wilt selecteren – wat een nogal specifieke elementselectie is – wilt u waarschijnlijk geen selector schrijven zoals .card > .content > img.hero
.
- Deze selector heeft een vrij hoge specificiteit van
(0,3,1)
waardoor deze moeilijk te overschrijven is naarmate uw code groeit. - Door te vertrouwen op de directe kindcombinator is deze nauw gekoppeld aan de DOM-structuur. Mocht de opmaak ooit veranderen, dan moet u ook uw CSS wijzigen.
Maar u wilt ook niet alleen img
als selector voor dat element schrijven, omdat daarmee alle afbeeldingselementen op uw pagina zouden worden geselecteerd.
Hierin de juiste balans vinden is vaak een hele uitdaging. Door de jaren heen hebben sommige ontwikkelaars oplossingen en oplossingen bedacht om u in situaties als deze te helpen. Bijvoorbeeld:
- Methodologieën zoals BEM schrijven voor dat je dat element een klasse
card__img card__img--hero
geeft om de specificiteit laag te houden, terwijl je toch specifiek kunt zijn in wat je selecteert. - Op JavaScript gebaseerde oplossingen zoals Scoped CSS of Styled Components herschrijven al uw selectors door willekeurig gegenereerde tekenreeksen (zoals
sc-596d7e0e-4
) toe te voegen aan uw selectors om te voorkomen dat ze zich richten op elementen aan de andere kant van uw pagina. - Sommige bibliotheken schrappen selectors zelfs helemaal en vereisen dat je de stylingtriggers rechtstreeks in de markup zelf plaatst.
Maar wat als je die allemaal niet nodig had? Wat als CSS je een manier zou bieden om behoorlijk specifiek te zijn over welke elementen je selecteert, zonder dat je selectors met een hoge specificiteit hoeft te schrijven of selectors die nauw gekoppeld zijn aan je DOM? Welnu, dat is waar @scope
in het spel komt en je een manier biedt om alleen elementen binnen een subboom van je DOM te selecteren.
Maak kennis met @scope
Met @scope
kunt u het bereik van uw selectors beperken. U doet dit door de scoping root in te stellen die de bovengrens bepaalt van de subboom die u wilt targeten. Met een scoping root set kunnen de ingesloten stijlregels – de zogenaamde scoped stijlregels – alleen selecteren uit die beperkte subboom van de DOM.
Als u bijvoorbeeld alleen de <img>
-elementen in de component .card
wilt targeten, stelt u .card
in als de scoping root van de @scope
at-regel.
@scope (.card) {
img {
border-color: green;
}
}
De bereikstijlregel img { … }
kan effectief alleen <img>
-elementen selecteren die binnen het bereik van het overeenkomende .card
element vallen.
Om te voorkomen dat de <img>
-elementen in het inhoudsgebied van de kaart ( .card__content
) worden geselecteerd, kunt u de img
selector specifieker maken. Een andere manier om dit te doen is door gebruik te maken van het feit dat de @scope
at-regel ook een scopinglimiet accepteert die de ondergrens bepaalt.
@scope (.card) to (.card__content) {
img {
border-color: green;
}
}
Deze stijlregel met bereik richt zich alleen op <img>
-elementen die tussen de elementen .card
en .card__content
in de stamboom zijn geplaatst. Dit type scoping – met een boven- en ondergrens – wordt vaak een donut-scope genoemd
De :scope
selector
Standaard zijn alle stijlregels met bereik relatief ten opzichte van de scoping root. Het is ook mogelijk om het scoping root-element zelf te targeten. Gebruik hiervoor de :scope
-selector.
@scope (.card) {
:scope {
/* Selects the matched .card itself */
}
img {
/* Selects img elements that are a child of .card */
}
}
Selectors binnen bereikstijlregels krijgen impliciet :scope
voorafgegaan. Als je wilt, kun je er expliciet over zijn, door zelf :scope
ervoor te zetten. Als alternatief kunt u de &
-selector vooraf laten gaan, vanuit CSS Nesting .
@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 */
}
}
Een scopinglimiet kan de :scope
pseudo-klasse gebruiken om een specifieke relatie met de scopingroot te vereisen:
/* .content is only a limit when it is a direct child of the :scope */
@scope (.media-object) to (:scope > .content) { ... }
Een scopinglimiet kan ook verwijzen naar elementen buiten hun scopingroot door :scope
te gebruiken. Bijvoorbeeld:
/* .content is only a limit when the :scope is inside .sidebar */
@scope (.media-object) to (.sidebar :scope .content) { ... }
Merk op dat de scoped-stijlregels zelf niet aan de subboom kunnen ontsnappen. Selecties zoals :scope + p
zijn ongeldig omdat daarmee elementen worden geselecteerd die niet binnen het bereik vallen.
@scope
en specificiteit
De selectors die u in de inleiding voor @scope
gebruikt, hebben geen invloed op de specificiteit van de opgenomen selectors. In het onderstaande voorbeeld is de specificiteit van de img
selector nog steeds (0,0,1)
.
@scope (#sidebar) {
img { /* Specificity = (0,0,1) */
…
}
}
De specificiteit van :scope
is die van een reguliere pseudo-klasse, namelijk (0,1,0)
.
@scope (#sidebar) {
:scope img { /* Specificity = (0,1,0) + (0,0,1) = (0,1,1) */
…
}
}
In het volgende voorbeeld wordt &
intern herschreven naar de selector die wordt gebruikt voor de scoping root, verpakt in een :is()
selector. Uiteindelijk zal de browser :is(#sidebar, .card) img
gebruiken als selector om de matching uit te voeren. Dit proces staat bekend als ontsuikeren .
@scope (#sidebar, .card) {
& img { /* desugars to `:is(#sidebar, .card) img` */
…
}
}
Omdat &
wordt ontsuikerd met behulp van :is()
, wordt de specificiteit van &
berekend volgens de :is()
specificiteitsregels : de specificiteit van &
is die van zijn meest specifieke argument.
Toegepast op dit voorbeeld is de specificiteit van :is(#sidebar, .card)
die van zijn meest specifieke argument, namelijk #sidebar
, en wordt daarom (1,0,0)
. Combineer dat met de specificiteit van img
– namelijk (0,0,1)
– en je krijgt (1,0,1)
als specificiteit voor de hele complexe selector.
@scope (#sidebar, .card) {
& img { /* Specificity = (1,0,0) + (0,0,1) = (1,0,1) */
…
}
}
Het verschil tussen :scope
en &
binnen @scope
Naast verschillen in de manier waarop specificiteit wordt berekend, is een ander verschil tussen :scope
en &
dat :scope
de overeenkomende scopingwortel vertegenwoordigt, terwijl &
de selector vertegenwoordigt die wordt gebruikt om de scopingwortel te matchen.
Hierdoor is het mogelijk om &
meerdere keren te gebruiken. Dit is in tegenstelling tot :scope
dat u slechts één keer kunt gebruiken, omdat u een scoping root niet binnen een scoping root kunt matchen.
@scope (.card) {
& & { /* Selects a `.card` in the matched root .card */
}
:scope :scope { /* ❌ Does not work */
…
}
}
Prelude-loze reikwijdte
Wanneer u inline stijlen schrijft met het <style>
-element, kunt u de stijlregels beperken tot het omsluitende bovenliggende element van het <style>
-element, door geen bereikbasis op te geven. Dit doe je door de prelude van @scope
weg te laten.
<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>
In het bovenstaande voorbeeld richten de scoped-regels zich alleen op elementen binnen de div
met de klassenaam card__header
, omdat die div
het bovenliggende element van het <style>
-element is.
@scope in de cascade
Binnen de CSS Cascade voegt @scope
ook een nieuw criterium toe: scoping proximity . De stap komt na specificiteit, maar vóór de volgorde van verschijnen.
Volgens specificatie :
Bij het vergelijken van declaraties die voorkomen in stijlregels met verschillende bereikwortels, wint de declaratie met het minste generatie- of zusterelement tussen de scopingwortel en het onderwerp van de bereikstijlregel.
Deze nieuwe stap is handig bij het nesten van verschillende varianten van een component. Neem dit voorbeeld, dat @scope
nog niet gebruikt:
<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>
Als je dat kleine stukje markup bekijkt, zal de derde link white
zijn in plaats van black
, ook al is het een kind van een div
waarop de klasse .light
is toegepast. Dit komt door het volgorde-van-verschijningscriterium dat de cascade hier gebruikt om de winnaar te bepalen. Het ziet dat .dark a
als laatste is verklaard, dus het wint van de .light a
-regel
Met het scoping nabijheidscriterium is dit nu opgelost:
@scope (.light) {
:scope { background: #ccc; }
a { color: black;}
}
@scope (.dark) {
:scope { background: #333; }
a { color: white; }
}
Omdat beide scoped a
dezelfde specificiteit hebben, treedt het scoping-nabijheidscriterium in werking. Het weegt beide selectors op basis van de nabijheid van hun bereikwortel. Voor dat derde a
is het slechts één sprong naar de .light
scopingwortel, maar twee naar de .dark
wortel. Daarom zal de a
selector in .light
winnen.
Slotopmerking: Selectorisolatie, niet stijlisolatie
Een belangrijke opmerking om te maken is dat @scope
het bereik van de selectors beperkt, het biedt geen stijlisolatie. Eigenschappen die overerven van kinderen, zullen nog steeds erven, voorbij de ondergrens van de @scope
. Eén zo'n eigenschap is de color
. Als je aangeeft dat er één in een donut-scope zit, zal de color
nog steeds worden overgenomen van kinderen in het gat van de donut.
@scope (.card) to (.card__content) {
:scope {
color: hotpink;
}
}
In het bovenstaande voorbeeld hebben het element .card__content
en zijn onderliggende elementen een hotpink
kleur omdat ze de waarde van .card
overnemen.
(Omslagfoto door rustam burkhanov op Unsplash )