CSS er ikke sort magi

Træk gardinerne tilbage på dine stilark

Hvis du er en webudvikler, er chancerne for, at du bliver nødt til at skrive nogle CSS fra tid til anden.

Når du først kiggede på CSS, virkede det sandsynligvis som en leg. Du tilføjede nogle grænser her, ændrede nogle farver der. JavaScript er den hårde del af frontend-udvikling, ikke?

Et sted under din progression som webudvikler ændrede det sig dog! Hvad der er værre er, at mange udviklere i front-end-samfundet er kommet til at afvise CSS som et legetøjssprog.

Sandheden er dog, at når vi rammer en væg, forstår mange af os ikke, hvad vores CSS gør under hætten.

I de første to år efter min bootcamp udførte jeg fuld stack JavaScript og dryssede i nogle CSS her og der. Som paneldeltager på JavaScript Jabber følte jeg altid, at JavaScript var mit brød og smør, så det var det, jeg brugte mest tid på.

Sidste år besluttede jeg dog at fokusere på frontend, og jeg indså, at jeg bare ikke var i stand til at fejlsøge mine stilark på samme måde som jeg gjorde min JavaScript!

Vi kan alle sammen bryde sammen med vittigheder om det, men hvor mange af os, der faktisk har taget sig tid til at prøve at forstå det CSS, vi skriver eller læser. Hvor mange af os har faktisk med rimelighed fejlagtigt et emne til det næste laveste abstraktionslag, da vi ramte en væg? I stedet tilfredsstiller vi os med det første StackOverflow-svar, hacks, eller vi lader bare problemet gå helt ud.

Alt for ofte forlades udviklere helt forundrede, når browseren gengiver CSS på måder, de ikke forventede. Det er dog ikke mørk magi, og som udviklere ved vi, at computere bare analyserer vores instruktioner.

Kendskab til interne kan også være nyttig til avanceret debugging og performance tuning. Mens mange konferencesamtaler diskuterer, hvordan man løser almindelige bugs, vil min tale (og dette indlæg) fokusere på hvorfor ved at tage et dybt dykk ned i browserinterne for at se, hvordan vores stilarter er parset og gengivet.

DOM og CSSOM

For det første er det vigtigt at forstå, at browsere indeholder en JavaScript-motor og en gengivelsesmotor. Vi vil fokusere på sidstnævnte. Vi diskuterer for eksempel detaljer, der vedrører WebKit (Safari), Blink (Chrome), Gecko (Firefox) og Trident / EdgeHTML (IE / Edge). Browseren gennemgår en proces, der inkluderer konvertering, tokenisering, lexing og parsing, som i sidste ende konstruerer DOM og CSSOM.

På et højt niveau kan du tænke på dem som følgende:

  • Konvertering: Aflæsning af rå byte af HTML og CSS fra disken eller netværket.
  • Tokenisering: Opdeling af input i bunker (fx: startmærker, slutkoder, attributnavne, attributværdier), striber irrelevante tegn såsom hvidafstand og linjeskift.
  • Lexing: Ligesom tokenizeren, men den identificerer også typen af ​​hvert token (dette token er et tal, dette token er en bogstavelig streng, dette andet token er en ligestillingsoperatør).
  • Parsing: Tar strømmen af ​​tokens fra lexeren, fortolker tokens ved hjælp af en bestemt grammatik og gør den til et abstrakt syntaks træ.

Når begge træstrukturer er oprettet, fastgør rendemotoren derefter datastrukturerne i det, der kaldes et render-træ, som en del af layoutprocessen.

Render-træet er en visuel repræsentation af dokumentet, der gør det muligt at male indholdet af siden i deres rigtige rækkefølge. Render trækonstruktion følger følgende rækkefølge:

  • Start ved roden af ​​DOM-træet, krydse hver synlig knude.
  • Udelad ikke-synlige knuder.
  • Find de passende matchende CSSOM-regler for hver synlig knude og anvend dem.
  • Afgiv synlige knudepunkter med indhold og deres beregnede stilarter.
  • Endelig udsender du et render-træ, der indeholder både indhold og stilinformation for alt synligt indhold på skærmen.

CSSOM kan have drastiske effekter på render-træet, men ingen på DOM-træet.

gengivelse

Efter layout og gengivelse af trækonstruktion kan browseren endelig gå videre til faktisk maling af skærmen og sammensætning. Lad os tage et kort øjeblik for at skelne mellem nogle terminologier her.

  • Layout: Omfatter beregning af, hvor meget plads et element tager, og hvor det er på skærmen. Forældreelementer kan påvirke børnelementer og undertiden omvendt.
  • Maleri: Processen med at konvertere hver node i render-træet til faktiske pixels på skærmen. Det indebærer at tegne tekst, farver, billeder, rammer og skygger. Tegningen udføres typisk på flere lag, og flere malingsrunder kan være forårsaget af, at JavaScript indlæses, der ændrer DOM.
  • Komposition: Handlingen med at udflate alle lag i det endelige billede, der er synlig på skærmen. Da dele af siden kan tegnes i flere lag, skal de tegnes til skærmen i den rigtige rækkefølge.

Maletid varierer afhængigt af renderingstræskonstruktion, og jo større bredden og højden på elementet er, jo længere er maletiden.

Tilføjelse af forskellige effekter kan også øge maletiden. Maleri følger den rækkefølge, at elementer stables i deres stablingskontekster (bagfra), som vi kommer ind på, når vi senere snakker om z-indeks. Hvis du er en visuel lærer, er der en stor malingsdemo.

Når folk taler om hardwareacceleration i browsere, henviser de næsten altid til accelereret sammensætning, som bare betyder at bruge GPU til at sammensætte indhold på en webside.

Komposition giver mulighed for temmelig stor hastighedsforøgelse i forhold til den gamle måde, som brugte computerens CPU. Egenskaben med ændring af vilje er en egenskab, som du kan tilføje, der drager fordel af dette.

Når du f.eks. Bruger CSS-transformeringer, giver egenskaben til ændring af ændring mulighed for at antyde, at browseren er, at et DOM-element transformeres i den nærmeste fremtid. Dette muliggør aflæsning af nogle tegne- og kompositionsoperationer til GPU, hvilket i høj grad kan forbedre ydelsen for sider med en masse animationer. Det har lignende gevinster for rulleposition, indhold, opacitet og placering øverst eller venstre.

Det er vigtigt at forstå, at visse egenskaber vil forårsage en relayout, mens andre egenskaber kun forårsager en ny maling. Selvfølgelig er præstation klogt det bedst, hvis du kun kan udløse en ny maling.

For eksempel vil ændringer af et elements farve kun male dette element, mens ændringer i elementets position medfører layout og maling af det element, dets børn og muligvis søskende. Tilføjelse af en DOM-knude forårsager layout og maling af noden igen. Store ændringer, såsom at øge skriftstørrelsen på et html-element, vil medføre en relayout og maling af hele træet igen.

Hvis du er som mig, er du sandsynligvis mere fortrolig med DOM end CSSOM, så lad os dykke lidt ind i det. Det er vigtigt at bemærke, at CSS som standard behandles som en renderblokerende ressource. Dette betyder, at browseren holder gengivelse af enhver anden proces, indtil CSSOM er konstrueret.

CSSOM er heller ikke 1 til 1 med DOM. Vis ingen, script-tags, metatags, hovedelement osv. Er udeladt, da de ikke afspejles i det gengivne output.

En anden forskel mellem CSSOM og DOM er, at CSS-parsning bruger en kontekstfri grammatik. Med andre ord har gengivelsesmotoren ikke kode, der udfylder manglende syntaks for CSS, som den vil, når man analyserer HTML for at oprette DOM.

Når man analyserer HTML, skal browseren tage hensyn til de omkringliggende tegn, og den har brug for mere end bare spec, da markeringen kunne indeholde manglende information, men vil stadig være nødvendigt at blive gengivet uanset hvad.

Lad os gøre det sammen med alt dette.

  • Browser sender en HTTP-anmodning om side
  • Webserver sender et svar
  • Browser konverterer svardata (bytes) til tokens via tokenisering
  • Browser gør tegn til noder
  • Browser omdanner noder til DOM-træet
  • Venter på CSSOM-trækonstruktion

Specificitet

Nu hvor vi har en bedre forståelse af, hvordan browseren fungerer under hætten, så lad os se på nogle af de mere almindelige forvirringsområder for udviklere. Først op, specificitet.

På et meget grundlæggende niveau ved vi, at specificitet bare betyder at anvende regler i den rigtige kaskade-rækkefølge. Der er dog mange måder at målrette mod et specifikt tag ved hjælp af CSS-vælgere, og browseren har brug for en måde at forhandle om hvilke stilarter der skal gives til et specifikt tag. Browsere tager denne beslutning ved først at beregne hver enkelt vælgers specificitetsværdi.

Desværre har specificitetsberegningen forvirret mange JavaScript-udviklere, så lad os tage et dybere dykk i, hvordan denne beregning foretages. Vi bruger et eksempel på en div med en klasse af "container". Hyggeligt inde i denne div vil vi have en anden div med en id "main". Inde i dette har vi et p-mærke, der indeholder et ankermærke. Uden at kigge foran, ved du, hvilken farve ankermærket bliver?

#main a {
  farve: grøn;
}
p a {
  farve: gul;
}
.container #main a {
  farve: lyserød;
}
div #main p a {
  farve: orange;
}
en {
  farve: rød;
}

Svaret er lyserødt med en værdi af 1,1,1. Her er de resterende resultater:

  • div #main p a: 1,0,3
  • #main a: 1,0,1
  • p a: 2
  • a: 1

For at bestemme antallet skal du beregne følgende:

  • Første nummer: Antallet af ID-vælgere.
  • Andet nummer: Antallet af klassevælgere, attributvælger (eks: [type = "tekst"], [rel = "nofollow"]) og pseudoklasser (eks:: hover,: besøgt).
  • Tredje nummer: Antallet af typevælgere og pseudo-elementer (eks: :: før, :: efter).

Så for en vælger, der ser sådan ud:

#header .navbar li a: besøgt

Værdien vil være 1,2,2, fordi vi har en ID, en klasse, en pseudoklasse og to type-vælgere (li, a). Du kan læse værdierne, som om de blot var et tal, ligesom 1,2,2 er 122. Kommerne er der for at minde dig om, at dette ikke er et base 10-system. Du kan teknisk set have en specificitetsværdi på 0,1,13,4 og 13 ville ikke smitte over, som et base 10-system ville gøre.

Positionering

For det andet vil jeg tage et øjeblik til at diskutere positionering. Positionering og layout går hånd i hånd, som vi så tidligere i dette indlæg.

Layout er en rekursiv proces, der kan udløses på hele render-træet som et resultat af en global stilændring eller gradvist, hvor kun beskidte dele af siden vil blive lagt ud over. En interessant ting at bemærke, hvis vi tænker tilbage på render-træet, er, at med absolut placering placeres objektet, der er lagt, i render-træet et andet sted end i DOM-træet.

Jeg bliver ofte spurgt om brug af flexbox kontra floats. Naturligvis er flexbox stor fra et brugervenlighedsmæssigt synspunkt, men når det anvendes til det samme element, vil et flexboxlayout gengives i cirka 3,5 ms, mens et flydende layout kan tage omkring 14ms. Så det lønner sig at følge med dine CSS-færdigheder lige så meget som du gør dine JavaScript-evner.

Z-Index

Til sidst vil jeg diskutere z-indeks. Først lyder det enkelt. Hvert element i et HTML-dokument kan være enten foran eller bag hvert andet element i dokumentet. Det fungerer også kun på positionerede elementer. Hvis du prøver at indstille et z-indeks på et element uden nogen specificeret position, gør det ikke noget.

Nøglen til debugging af z-indeksproblemer er forståelse af stablingskontekster og altid at starte ved stablingskontekstens rodelement. En stablingskontekst er kun en tredimensionel konceptualisering af HTML-elementer langs en imaginær z-akse i forhold til brugeren, der vender mod visningen. Med andre ord er det grupper af elementer med en fælles forælder, der bevæger sig fremad eller bagud sammen.

Hver stablingskontekst har et enkelt HTML-element som dets rodelement, og når z-indeks- og positionsegenskaber ikke er involveret, er reglerne enkle. Stabelrækkefølgen er den samme som rækkefølgen af ​​udseende i HTML.

Du kan dog oprette nye stablingskontekster med andre egenskaber end z-indeks, og det er her ting bliver kompliceret. Når opacitet er mindre end én, skal filter, når dens værdi er noget andet end ingen, og mix-mix-mode, når dens værdi er noget andet end normalt, skaber faktisk nye stablingskontekster.

Bare en påmindelse, blandetilstand bestemmer, hvordan pixels på et specifikt lag interagerer med de synlige pixels på lagene under det.

Transformeegenskapen udløser også en ny stablingskontekst, når dens værdi ikke er nogen. For eksempel skala (1) og translate3d (0,0,0). Igen, som en påmindelse bruges skaleegenskapen til at justere størrelse, og translate3d udløser GPU til handling for CSS-overgange, der gør dem glattere.

Så du har muligvis stadig ikke øje for design, men forhåbentlig går du nu væk fra en CSS-guru! Hvis du er interesseret i at gå endnu længere, har jeg samlet yderligere ressourcer, som jeg også brugte her.