Nader bekeken: FaCSSt - CSS en performance

Eerder dit jaar stond Frontend United op de agenda. Frontend United is een jaarlijks terugkerende Europese conferentie, welke als doel heeft om frontend developers, designers en Drupal theme developers bij elkaar te brengen en kennis te delen.

Dit jaar vond Frontend United in Utrecht plaats. PanCompany was community sponsor en ik was als afgevaardigde van PanCompany erbij.

Verschillende onderwerpen zijn op de conferentie de revue gepasseerd zoals: Vue.js, Preact, Data & Privacy en React. Ik wil één talk in het bijzonder er uitlichten: FaCSSt - CSS and Performance, gepresenteerd door Harry Roberts.

Er werd afgetrapt met een quote: “The Trainline reduced latency by 0.3 seconds across their funnel and customers spent an extra £8 million ($11.5 million) a year.” Het belangrijke hieraan is dat 0,3 seconden bijna 8 miljoen pond extra kan opleveren! De vraag is vervolgens: Waar kun je die 0,3 seconden het snelste winnen? Het antwoord hierop is: CSS. Het is dus mogelijk om de performance van je website of applicatie te verbeteren door de CSS aan te pakken.

Je vraagt je waarschijnlijk af: waarom CSS en niet JavaScript, aangezien in de huidige tijd alles om JavaScript draait. Maar het blijkt dat CSS toch voor de meeste vertraging kan zorgen. Volgens HTTP Archive zijn 3 van de top 5 meest vertragende render factoren verwant aan CSS.

CSS render

De reden dat CSS een vertragende factor is, is omdat CSS-onderdeel is van het zogeheten kritieke pad. 

Het kritieke pad is het verplichte pad dat een browser moet doorlopen om pixels op het scherm te laten zien. De server geeft de HTML en de CSS zodat er iets op het scherm gepresenteerd kan worden. Optioneel kan daar Javascript bij komen. In dat geval voegt de browser de HTML en Javascript, die de HTML beïnvloedt, en maakt daar de DOM (Document Object Model) van. Aan de andere kant worden de CSS en Javascript, die de CSS beïnvloedt, samengevoegd en maakt daar de CSSOM (CSS Object Model) van. Op het moment dat de DOM en CSSOM zijn gegenereerd, worden deze als het ware op elkaar gelegd en wordt de “Render Tree” gemaakt. Pas als die is gemaakt kan de browser beginnen met het tonen van informatie op het scherm en worden dingen als fonts en achtergrond afbeeldingen gedownload. Het staat dus buiten kijf dat als er iets niet goed gaat in deze flow of iets voor vertraging zorgt, het langer duurt voor er iets wordt getoond.

Krtitieke pad

Bestandsgrootte

Als ontwikkelaar weet je dat de grootte van bestanden invloed heeft op de snelheid van het laden van een website. Hoe lang denk je dat een Moto G4 doet over het verwerken van een 1 MB CSS bestand? Het blijkt dat deze er 200ms over doet om een CSS-bestand van 1 MB te verwerken en dan hebben we het nog niet eens over het tonen van de styles. 200ms Is natuurlijk niet veel, maar kan een enorm verschil maken bij het laden van een website.

Base64 is een van de dingen die een CSS-bestand zo groot maakt. Een aantal jaar geleden werd verteld dat we beter base64 konden gebruiken om afbeeldingen in te laden dan een JPG of PNG bestand. Dit omdat we met HTTP1 we maar 6 verbindingen tegelijk naar de server hebben en dus maar 6 bestanden tegelijk in kunnen laden. Dus om de verbindingen te sparen voor belangrijke bestanden moeten we die niet verspillen aan afbeeldingen. Met alle goede bedoelingen werd dit verteld, maar het is nogal een tegenstrijdige oplossing aangezien base64 CSS-bestanden aanzienlijk groter maken. Het betekent overigens niet dat je het helemaal niet moet doen want voor kleine afbeeldingen en icoontjes is dit geen enkel probleem. Dit is bijvoorbeeld het logo van PanCompany in base64 en zoals je ziet is dat enorm. Het logo in base64 bestaat uit 22728 tekens en opgeslagen als bestand beslaat het 23KB. 23 KB is normaal gesproken niet veel, maar met meerdere van dit soort afbeeldingen drukt het aardig op het formaat van je CSS-bestand.

logo Pan base64

@import

Hiermee bedoel ik niet de SaSS @import want dat is iets heel anders. Het gaat hier om de vanilla CSS @import. Dit is ook een grote bottleneck bij het ophalen van de CSS-bestanden. @import gebruik je misschien om individuele CSS-bestanden te cachen en dus heb je een basis CSS-bestand waar je bijvoorbeeld je grid, reset, fonts en components inlaadt via @import. Als je die vervolgens aan een browser geeft, dan denkt de browser dat de website getoond kan worden, echter bij het openen van het bestand moet hij ineens nog 4 bestanden ophalen. Het probleem hier is dat de browser pas iets gaat laten zien als hij alle CSS-bestanden binnen heeft. Dus pas als het basis bestand binnen is, kunnen de andere bestanden worden opgehaald. Het duurt dus nog langer voordat de browser iets kan laten zien. Door de @import weg te halen en de bestanden los aan te roepen in de HTML via een link-selector worden de bestanden tegelijk opgehaald en kan de browser veel eerder al beginnen met het tonen van de website.

Selectors

Het optimaliseren van de selectors in je CSS is één van de laatste dingen die je moet doen aangezien ze al erg snel zijn. Browsers lezen selectors van rechts naar links in plaats van links naar rechts zoals wij doen.


.from > #right .to-left {}

Het mooie hiervan is, is dat een browser dus kijkt naar het laatste stukje omdat dat de selector is waar hij iets mee moet doen. Op het moment dat de browser gaat zoeken, kijkt hij dus of de laatste selector ergens op de pagina aanwezig is alvorens hij gaat kijken wat zijn parent (bovenliggende selector) is. Mocht het voorkomen dat de key-selector, zoals deze wordt genoemd, niet aanwezig is dan weet de browser dat hij niet verder hoeft te zoeken en de regel helemaal kan overslaan.

Zoals aangegeven is het optimaliseren van selectors het laatste wat je moet doen omdat browsers al zo slim zijn om van rechts naar links te lezen. Maar er zit wel degelijk verschil in de snelheid van selectors en selector-reeksen.


.top-bar-section {}

Dit is een snelle selector, de browser hoeft alleen maar te zoeken naar deze class (top-bar-section) en is vervolgens klaar. De browser heeft in dit geval een specifiek doel, namelijk deze class vinden. Het voorbeeld hieronder kost een browser bijvoorbeeld meer tijd.


.salarismeter p {}



Deze selector is minder snel. De browser gaat namelijk eerst kijken naar alle "p" tags in de HTML en die kunnen er veel zijn. Vervolgens gaat de browser pas na of deze "p" tags voorkomen in een element met de class ".salarismeter".


.left-submenu * {}

Deze selector is een weer een stuk langzamer. Dit geeft namelijk tegen de browser aan dat de browser moet zoeken naar elke selector in de HTML om vervolgens na te gaan of deze voorkomt in een element met de class ".left-submenu". Met alle selectors bedoel ik ook echt alle, dus ook de html, body, meta tags enz.


* {}

Deze selector is in tegenstelling tot de vorige erg snel. Er is namelijk geen betere manier om tegen een browser te zeggen geef me alles. Het hangt dus heel erg af van waar je de ster-selector gebruikt.

Dan heb je nog child-selectors die aangegeven worden met een >. Hoe zit het daarmee? Als je bijvoorbeeld deze twee selector reeksen hebt, welke zou dan sneller zijn?


nav > ul > li > a {}           .nav ul li a {}

De eerste is hierbij het snelste. Ik zal het voorbeeld op mezelf betrekken. De tweede reeks vraagt namelijk: “Wouter heet één van jouw voorvaders Victor” waar de eerste reeks vraagt of mijn vader Victor heet. Op de tweede vraag is het simpel ja of nee en kan de browser door, maar bij de eerste moet de browser als het ware een heel archief door om te kijken of er daadwerkelijk één van mijn voorvaders Victor heette. Wanneer je dus specifiek doelt op één element wat in een ander element zit, is het beter om het eerste voorbeeld te gebruiken.

Link in body

<link> elementen moesten tot voor kort in de <head> worden gezet, maar nu kunnen we ze ook in de <body> plaatsen wat effect heeft op het renderen van webpagina’s. Normaal zou ik alle CSS in een bestand zitten en deze wordt op iedere pagina ingeladen. Echter zit alle CSS voor de gehele website in dit bestand en dus zit er een hoop overbodige CSS in die ook ingeladen wordt en dus het laden vertraagt.


<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Title</title>     <link rel="stylesheet" href="styles.css"> </head> <body>     <div class="header"></div>     <div class="navigation"></div>     <div class="main-content"></div>     <div class="footer"></div> </body> </html>

 Je kunt ervoor kiezen om de CSS op te splitsen in losse componenten en alleen degene in te laden die je nodig hebt zodat je geen formulier CSS inlaadt terwijl er geen formulieren op de pagina voor komen. Dat voorkomt het overbodig inladen van CSS en het aantal bestanden en de grootte.


<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Title</title>     <link rel="stylesheet" href="reset.css">     <link rel="stylesheet" href="header.css">     <link rel="stylesheet" href="navigation.css">     <link rel="stylesheet" href="main.css">     <link rel="stylesheet" href="footer.css"> </head> <body>     <div class="header"></div>     <div class="navigation"></div>     <div class="main-content"></div>     <div class="footer"></div> </body> </html>

Echter zorgt het er nog steeds voor dat het lang duurt voor de browser kan beginnen met iets te laten zien op de pagina. Omdat de browser pas begint iets te laten zien op het moment dat alle CSS binnen is.

Maar nu laten browsers de html onder een <link> element niet zien. Daardoor kun je als ontwikkelaar nu dit doen:


<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Title</title>     <link rel="stylesheet" href="reset.css"> </head> <body>     <link rel="stylesheet" href="header.css">     <div class="header"></div>     <link rel="stylesheet" href="navigation.css">     <div class="navigation"></div>     <link rel="stylesheet" href="main.css">     <div class="main-content"></div>     <link rel="stylesheet" href="footer.css">     <div class="footer"></div> </body> </html>

Nu is het mogelijk om het CSS-bestand van het component precies voor het component op de pagina te zetten waardoor de browser het CSS-bestand ophaalt voor een component en vervolgens het component laat zien en doorgaat naar de volgende. Hierdoor wordt de pagina stuk voor stuk ingeladen en ziet de gebruiker veel eerder al iets van de pagina. Hierdoor voelt het laden een stuk minder lang en is het ook minder lang.

Jake Archibald heeft een mooi artikel hierover geschreven met goede voorbeelden en is een aanrader om te lezen: https://jakearchibald.com/2016/link-in-body/

Het plaatsen van een <link> element was al mogelijk in IE, Edge en Firefox echter, hoewel wel geaccepteerd, werkte dit tot voor kort niet in Chrome en Safari. Het had geen effect op de laadtijd van de website maar in plaats van element voor element te laten zien werd het standaard gedrag vertoond, namelijk een witte pagina tot alles binnen is. Chrome versie 69 zal deze feature aan boord hebben en dus zal de website in Chrome ook sneller laden.

FireFox heeft nog een kleine bug als het gaat om <link> elementen in de <body>. Zoals gezegd laat de browser stukje voor stukje van een website zien omdat bij het laten zien van de pagina hij steeds op een <link> element stuit en dat eerst moet afhandelen. In FireFox wil dit wel eens mis gaan. De browser gaat dan gewoon door met het tonen van de hele pagina en stopt niet bij ieder <link> element. Het kan dan dus voorkomen dat delen van de website voor een paar seconde onopgemaakt zijn omdat de CSS nog niet is opgehaald. Er is een kleine niet al te mooie hack hiervoor en dat is de volgende:


    <link rel="stylesheet" href="header.css"><script> </script>     <div class="header"></div>

Door een <script> element met een spatie (hij mag niet leeg zijn) achter het <link> element te plaatsen zeg je nog duidelijker tegen de browser stop met het laten zien van wat hieronder staat totdat de CSS binnen is. Dit geldt voor alle <script> elementen in de <body>, de browser zal dit nooit uitvoeren terwijl hij bezig is om CSS op te halen. De reden hiervoor is dat de Javascript in zo’n <script> element mogelijk de CSS aan gaat passen als deze uitgevoerd wordt. Dat brengt ons terug naar het kritieke pad waarin de CSSOM wordt gemaakt in combinatie met eventuele Javascript nadat de CSS is opgehaald. Met deze oplossing werkt FireFox ook mee en andere browsers hebben hier geen last van omdat het bijdraagt aan het normale gedrag dat een browser vertoond.

Zoals gezegd is het optimaliseren van selectors niet het eerste wat je moet gaan doen als je een website sneller wilt maken. Het kan een bijdrage leveren, maar doe dit als allerlaatste. Het begint allemaal bij het kritieke pad dat de browser aflegt om een website te tonen. Optimaliseer dit pad, voorkom grote CSS-bestanden en blokkerende CSS-bestanden met @import. Wat daaraan bijdraagt is het gebruik van base64, probeer dat zoveel als mogelijk te vermijden. Begin met experimenteren met het plaatsen van <link> elementen in de <body>, dit zie je al terug bij verschillende component-based frameworks zoals Stencil van Ionic. Zelfs voor een platte website kan dit al veel doen voor de snelheid.

 

naar overzicht

Wilt u reageren of meer weten?

Heeft u iets in dit artikel gelezen dat uw interesse gewekt heeft? Laat het ons weten!

Deze post delen?

Trotse winnaar van een
FD Gazellen Award
2014 t/m 2018

© 2018 | Europalaan 12a | 5232BC 's-Hertogenbosch | T: +31 (0)85 0290550 | E: info@pancompany.com