CSS Nesting, spesifisitet, og deg

Kilde node: 1016564

CSS Nesting, spesifisitet og deg

Native CSS-nesting kommer snart til nettlesere. Med nesting, som du kanskje er kjent med fra Sass eller Less, kan du i stor grad kutte ned på å skrive repeterende velgere. Men du kan også virkelig jobbe deg inn i et hjørne hvis du ikke er forsiktig. Dette er en oversikt over hvordan du kan bruke det allerede i dag, fallgruvene og hvordan du unngår dem.

Hvordan ser CSS Nesting ut?

Hvis du kommer fra Sass eller Less, er du kanskje kjent med hekking som ser slik ut:

div { background: #fff; p { color: red; }
}

Den samme blokken skrevet i naturlig nestet CSS ser slik ut:

div { background: #fff; & p { color: red; }
}

Du vil legge merke til at det nå er et "&" foran p. Det er "nesting selector", og den forteller nettleseren du vil neste p i div. Du kan bare legge til nestevelgeren i begynnelsen. Vi har det også i Sass, men du trenger det bare når du vil legge til noe til den tidligere velgeren, som en klasse:

div { background: #fff; &.red { color: red; }
}

Og dette er faktisk nøyaktig det samme i native CSS-nesting. Tenk på det som om & håndheves bare for hver velger. Bare husk at i CSS & kommer alltid først.

Nå er det en måte å plassere & et annet sted, og det er ved å bruke hekke-at-regelen, @nest. Ved å sette foran en velger med @nest du kan velge hvor du vil at velgeren skal nestes.

div { background: #fff; @nest section & { color: red; }
}

I koden ovenfor gir du ikke seksjonen fargen rød, men du oppretter en ny velger section div (& erstattes av velgeren over den) som har en css-stil.

Du kan bare legge til nestevelgere etter all din vanlige CSS. Eventuelle CSS-egenskaper etter nestevelgeren vil bli ignorert. Så følgende er ugyldig:

div { background: #fff; & p { color: red; } border: 1px solid;
}

Til slutt kan du også neste medieforespørsler ved å sørge for at du bruker nestevelgeren til å starte en "regelsett"-blokk på nytt.

div { flex-direction: column; @media (min-width: 40rem) { & { flex-direction: row; } }
}

Som før & vil bli erstattet med foreldrevelgeren, så koden ovenfor ender opp med å være den samme CSS som denne:

div { flex-direction: column;
}
@media (min-width: 40rem) { div { flex-direction: row; }
}

Bruker den i dag med postcss-preset-env

CSS Nesting støttes ennå ikke i alle nettlesere, men hvis du bruker PostCSS du kan installere PostCSS Preset Env plugin og PostCSS vil konvertere din nestede CSS til CSS som er forstått av nettlesere i dag.

For å bruke PostCSS trenger du et byggesteg, for eksempel med Webpack, Parcel eller Gulp. Du finner en oversikt over oppsett i PostCSS-dokumenter om bruk, så velg noe som fungerer for deg.

Prøv det

For å leke med PostCSS Preset Env, sjekk ut lekeplassen deres, som samler ting på farten for deg.

Et lite eksempel

En enkel måte å komme i gang med det i ditt eget prosjekt er å bruke kommandolinjegrensesnittet ved å installere PostCSS CLI og postcss-preset-env:

npm install postcss postcss-cli postcss-preset-env --save-dev

Og lage en postcss.config.js fil som ser slik ut:

const postcssPresetEnv = require('postcss-preset-env'); module.exports = { plugins: [ postcssPresetEnv() ]
}

Så løper postcss i terminalen din i samme mappe som konfigurasjonsfilen, og den vil automatisk hente den.

 postcss path/to/input.css -o path/to/output.css 

Ok, så det er slik du kan bruke det i dag, men når du gjør det er det noe å være klar over, og det er spesifisitet.

Spesifisitet: den store fallgruven

For å forstå hva som skjer her, må jeg innrømme at jeg i hekkeeksemplene ovenfor gjorde ting litt enklere enn hvordan de egentlig er.

Når en velger er løst, bryter nettleseren en is() velger rundt en hvilken som helst foreldrevelger, og det påvirker spesifisiteten.

De is() velger

Hvis du ikke er kjent med den (den er relativt ny), kan den is() selector lar deg pakke inn en haug med forskjellige velgere for å forhindre duplisering:

main h1,
main h2,
main h3 { font-family: fantasy;
} main :is(h1, h2, h3) { font-family: fantasy;
}

De to eksemplene gir samme resultat, men legg merke til hvordan vi i det andre eksemplet bare har skrevet «hoved» én gang, mens vi i det første eksemplet måtte skrive det tre ganger. Når det gjelder spesifisitet, er det :is() velgeren får sin spesifisitet fra det mest spesifikke elementet i listen.

Ukjent med spesifisitet?

Hvis et element er målrettet av flere velgere, bruker nettleseren spesifisiteten til velgeren for å bestemme hvilken stil som skal brukes. Hver velger har en spesifisitet som bestemmes av hva du bruker i den velgeren: elementer, ID-er, klasser osv.

Det ser vanligvis ut som tre bøtter, slik: 1.2.3. Den første er antall id-er, den andre er antall klasser, pseudoklasser og attributtvelgere, og den siste bøtten har elementer og pseudo-elementer.

En enkelt verdi på et høyere nivå er viktigere enn alle nivåer under det. For eksempel kan du ha en velger med 1000 klassenavn, og en velger med en enkelt ID, sistnevnte vil fortsatt være mer spesifikk.

Hvis du ønsker å få en bedre følelse for spesifisitet, bygde jeg en CSS-spesifisitetskalkulator som støtter velgere helt opp til nivå 5. Skriv inn velgerne dine og se hvilken spesifisitet de ender opp med.

Tilbake til den :is() velger

Så for vår :is(h1, h2, h3) for eksempel er spesifisiteten lav, 0.0.1, siden det bare er elementer der inne. Men for velgeren :is(#start, h1), spesifisiteten til hele :is() erklæring skyter opp til 1.0.0 på grunn av id.

Dette er lett å oppdage hvis du må skrive ut hele velgeren hver gang, men det kule med hekking er at du ikke lenger trenger å gjøre det.

Det betyr dessverre at du kan ende opp med massivt spesifikke CSS-velgere og ikke vite det!

La oss for eksempel se på dette "enkle" eksemplet på en liste over nyhetssider. Fordi de er deres egen komponent, er det fornuftig å holde alt sammen.

main { & #intro { & .newsitems { & div.newsitem { & h2 { } & p { &.meta { & span.date { } } & + p { } } & a { } } } }
}

Fordi hver velger på egen hånd ser så enkel ut, og du kan bare fortsette å hekke, før du vet ordet av det, ser du på en faktisk css-regel som ser slik ut:

main #intro .newsitems div.newsitem p.meta + p { }

Som har en spesifisitet av 1.3.4 og vil overskrive de fleste andre CSS-velgere du skriver.

Gjør dette ofte nok, og før du vet ordet av det, vil du finne deg selv å lage enda mer kompleks velger eller legge til !important et sted for å få det til å fungere riktig, og da har du tapt.

Forhindre spesifisitetsproblemer

Så fristende som det er å bare fortsette å hekke (det er så enkelt! Og du må skrive så lite!), for å forhindre spesifisitetsproblemer vil du begrense hvor dypt du hekker og bryte ut av hekkingen når du kan.

Begrens hekkingen

Verktøy kan hjelpe deg ved å advare deg når du hekker for dypt. Til stylelint der er maks-hekkedybde regel, for eksempel.

Helt uvitenskapelig, men å se på min egen kode som setter grensen til 3 ser ut til å fungere best. Dette lar deg definere og style en beholder på toppnivå, dens barn og dens barns barn.

Bryt ut av hekking

Hvis du begrenser hvor dypt du kan hekke noe, ønsker du også å bryte ut av hekkingen så fort som mulig. Tenk på nestet CSS som et selvstendig sett med styling, nesten som en komponent. Hvis et av underelementene skal være deres egen komponent, start også en ny neste "kontekst".

Hvis vi tar eksemplet ovenfor, vil jeg si at følgende elementer alle fortjener sin egen nye kontekst:

  • main
  • #intro
  • .newsitems
  • div.newsitem

Men siden hver av disse er i foreldrene sine, hvor legger du til sin egen styling? I sin egen blokk, eller nestet i sin forelder.

For å gjøre det litt klarere, la oss si at newsitems-blokken har tre newsitems i seg, og vi vil ha dem i en horisontal fleksibel layout, slik at elementene er side ved side.

<div class="newsitems"> <div class="newsitem"> <h2>Title</h2> <p class="meta">posted at <span class="date">date</span> <p>Description</p> <a href="">Link</a> </div> <div class="newsitem"> <h2>Title</h2> <p class="meta">posted at <span class="date">date</span> <p>Description</p> <a href="">Link</a> </div> <div class="newsitem"> <h2>Title</h2> <p class="meta">posted at <span class="date">date</span> <p>Description</p> <a href="">Link</a> </div>
</div>

For det .newsitems, setter vi opp et fleksibelt oppsett:

.newsitems { display:flex; justify-content: flex-start; align-items: flex-start; gap: 1rem;
}

For å gjøre nyhetssidene i samme bredde (for det meste) kan vi legge til flex: 1 1 33% til dem, men det gir bare mening i sammenheng med .newsitems. Vi kan bruke en singel .newsitem andre steder, og derfor er ikke "fleksen" en del av stylingen for den enkelte nyheten. Mens vi gjør det, kan vi gjøre velgeren vi bruker litt mindre spesifikk ved å endre & .newsitem til & > div.

.newsitems { display:flex; justify-content: flex-start; align-items: flex-start; gap: 1rem; & > div { flex: 1 1 33%; }
} .newsitem { & h2 { }
}

Dessverre, nå har vi to steder hvor styling skjer, og de er kanskje ikke så pent side om side i din egen kode. Ideelt sett ville vi bare stylet hvert element én gang for å beholde en bedre oversikt.

Et potensielt problem er det .newsitems > div er mer spesifikk enn .newsitem (0.1.1 versus 0.1.0) så enhver styling i singelen .newsitem vil bli overskrevet av CSS i den nestede velgeren. Vanligvis dette er hva du vil, men ikke alltid.

En mulig måte å omgå dette på er å være strengere når det gjelder stylingen din, og det er et par måter, hver med fordeler og ulemper.

  • Del mellom layoutstiler og komponentstiler
  • Hold foreldrene tomme
  • Stilkomponenter frittstående

Del mellom layoutstiler og komponentstiler

Noen CSS brukes til å definere oppsettet, som position og flex, mens andre brukes til å definere stilen, som background og font-size. Du kan beholde layoutlogikken i den nestede velgeren og stillogikken i toppnivåvelgeren:

.newsitems { display:flex; justify-content: flex-start; align-items: flex-start; gap: 1rem; & > div { flex: 1 1 33%; }
} .newsitem { background: #fff; color: #332; }

Nå er stilen til komponenten frikoblet fra layouten i en overordnet komponent, noe som gjør den mer fleksibel. Dette krever litt disiplin og du må lete på forskjellige steder avhengig av om du vil endre layout eller stil.

Hold foreldrene tomme

Når et element bare brukes i ett overordnet element, kan du holde det øverste nivået av nestet tomt og bare bruke det til å definere velgeren, slik:

.newsitems { & > div { flex: 1 1 33%; background: #fff; }
} .newsitem { & h2 { }
}

På denne måten styler du alltid innholdet i en komponent (som det var) og bruker nesting-funksjonene utelukkende som et middel til å organisere CSS-en din.

Ulempen her er at det ikke alltid vil fungere, og hvis det er det samme .newsitems vises også i en sidefelt et sted du ender opp med å duplisere all stylingen deres for sidefeltet. Selv om noe av det kan variere, kan du ende opp med duplisering som kan unngås.

Stilkomponenter frittstående

Dette er egentlig det omvendte, der all stylingen din skjer inne i en komponent, og du bruker tilleggsklasser for å håndtere plassering hos forskjellige foreldre:

.newsitem { background: #fff; &.in-newsitems { flex: 1 1 33%; } &.in-sidebar { flex: 1 0 100%; } & h2 { }
}

Dette holder alt samlokalisert, men legger til kompleksiteten ved å måtte bruke flere klasser avhengig av hvor komponenten din er plassert, slik at du ikke bare kan slippe HTML et annet sted i DOM.

Hvilken av disse tre som fungerer best avhenger av hva du foretrekker og prosjektet du jobber i. Det som er viktig å ta bort er at det lønner seg å være bevisst om hvordan du bruker CSS Nesting. Gjør det til en gratis for alle, og du vil ende opp med CSS som er veldig vanskelig å vedlikeholde, men brukt med vilje er det en fantastisk måte å organisere CSS og gjenta deg selv mindre.

Krysser fingrene for at det lander i nettlesere snart!

Kilde: https://kilianvalkhof.com/2021/css-html/css-nesting-specificity-and-you/

Tidstempel:

Mer fra CSS triks