CSS moderno per le prestazioni: content-visibility, contain e container queries

Come content-visibility rimanda il rendering dei contenuti fuori schermo, come contain confina il ricalcolo nei componenti, cosa cambiano le container queries e quali script si possono sostituire con il CSS.

Una pagina lunga con le prime sezioni evidenziate e le successive tratteggiate, accanto a due schede che si adattano al contenitore

Per anni l’ottimizzazione del CSS ha riguardato soprattutto il peso dei fogli di stile e il modo in cui vengono caricati: ridurre le regole inutilizzate, estrarre il CSS critico, evitare che un foglio di stile blocchi la prima visualizzazione. Sono ancora temi centrali. Ma negli ultimi anni il CSS ha acquisito anche strumenti che agiscono sul lavoro del browser dopo il caricamento: proprietà che gli permettono di saltare il rendering di ciò che non è visibile, di limitare il ricalcolo a una parte della pagina, di adattare i componenti senza ricorrere a JavaScript.

In questo articolo vediamo le tre più utili per le prestazioni, content-visibility, contain e le container queries, insieme ad altre funzionalità moderne che permettono di eliminare script, con indicazioni su dove usarle, dove evitarle e come verificare che servano davvero.

Il lavoro del browser nelle pagine lunghe

Come abbiamo visto a proposito del percorso critico di rendering, dopo aver scaricato HTML e CSS il browser calcola gli stili di ogni elemento, ne determina posizione e dimensione, e disegna la pagina. Per impostazione predefinita lo fa per tutta la pagina, anche per le parti che l’utente vedrà solo dopo aver scorso a lungo, o che non vedrà mai. In una pagina breve la differenza è trascurabile. In un articolo lungo con centinaia di commenti, in una pagina di categoria con decine di schede di prodotto, in una pagina costruita con un costruttore di pagine che genera migliaia di elementi, il rendering iniziale può richiedere centinaia di millisecondi sul main thread di uno smartphone.

Quel lavoro ritarda la prima visualizzazione e, soprattutto, si ripete: ogni volta che qualcosa cambia la disposizione della pagina, il browser deve verificare quali elementi sono coinvolti, e più la pagina è grande più la verifica costa. È uno dei motivi per cui le interazioni sulle pagine molto lunghe tendono ad avere un INP peggiore.

content-visibility: saltare ciò che non si vede

La proprietà content-visibility con il valore auto dice al browser che il contenuto di un elemento può essere ignorato finché l’elemento è lontano dallo schermo. Il browser non calcola il layout né disegna i suoi figli; li elabora quando l’elemento si avvicina all’area visibile durante lo scorrimento. Il contenuto resta nel documento: è accessibile agli screen reader, può essere trovato con la ricerca del browser, è raggiungibile con i collegamenti interni. Cambia solo il momento in cui il browser se ne occupa.

Confronto tra una pagina lunga senza content-visibility, in cui il browser elabora tutte e sei le sezioni, e la stessa pagina con content-visibility auto, in cui elabora solo le due sezioni visibili e salta le altre usando una dimensione stimata di 74 pixel; il rendering iniziale passa, in valori illustrativi, da 180 a 60 millisecondi
Con content-visibility: auto il browser elabora le sezioni fuori schermo solo quando si avvicinano.

C’è un dettaglio importante. Finché il contenuto non viene elaborato, il browser non conosce la sua altezza, e tratterebbe l’elemento come se fosse alto zero. La pagina risulterebbe più corta del vero e la barra di scorrimento salterebbe man mano che le sezioni vengono elaborate. Per evitarlo si usa contain-intrinsic-size, che fornisce una dimensione stimata. Con la parola chiave auto il browser ricorda la dimensione reale dopo la prima elaborazione e la usa al posto della stima.

/* Commenti e sezioni in fondo all’articolo */
.commentlist > li,
.articoli-correlati,
.site-footer {
	content-visibility: auto;
	contain-intrinsic-size: auto 600px;
}

La stima non deve essere precisa, ma dovrebbe essere ragionevole: se è molto lontana dalla realtà, la lunghezza della pagina cambia sensibilmente durante lo scorrimento. Non è un problema per il CLS nella maggior parte dei casi, perché gli spostamenti avvengono fuori dallo schermo, ma può rendere lo scorrimento meno prevedibile, soprattutto con la barra di scorrimento trascinata.

Dove usarlo e dove no

content-visibility: auto dà benefici quando ci sono molti contenuti fuori schermo e quando quei contenuti sono complessi. I candidati tipici sono le liste di commenti, le sezioni di articoli correlati, le lunghe pagine di categoria o di archivio, i piè di pagina ricchi, le schede di prodotto in fondo alla pagina, le tabelle di specifiche. In questi casi il risparmio sul rendering iniziale può essere significativo.

Va invece evitato sugli elementi visibili al caricamento, e in particolare su quelli che contengono l’elemento LCP: non c’è nulla da rimandare, e si aggiunge solo un passaggio. È poco utile su elementi piccoli e semplici, dove il costo evitato è minimo, e va applicato con attenzione agli elementi il cui contenuto viene misurato da script, come alcuni caroselli o gallerie, che potrebbero leggere dimensioni sbagliate mentre il contenuto non è ancora elaborato. Un’ultima attenzione riguarda le ancore: saltare con un collegamento interno a una sezione lontana funziona, ma le sezioni intermedie vengono elaborate con la dimensione stimata, e la posizione finale può spostarsi leggermente se le stime sono imprecise.

contain: confinare il ricalcolo

La proprietà contain è lo strumento su cui si basa content-visibility, e si può usare anche da sola. Dichiara al browser che un elemento è indipendente dal resto della pagina sotto uno o più aspetti, e gli permette di limitare il lavoro quando qualcosa cambia al suo interno. I valori principali sono quattro:

  • layout: la disposizione interna dell’elemento non influenza quella esterna, e viceversa;
  • paint: il contenuto non viene disegnato fuori dai bordi dell’elemento, e se l’elemento è fuori schermo il suo contenuto non va disegnato;
  • size: la dimensione dell’elemento non dipende dal suo contenuto, e va quindi indicata esplicitamente;
  • style: alcuni effetti, come i contatori, non escono dall’elemento.

Il valore content equivale a layout paint style ed è il più comodo nella pratica: non richiede di fissare la dimensione e dà al browser le garanzie più utili. Il valore strict aggiunge anche size, ed è adatto solo a elementi con dimensioni note, come un riquadro pubblicitario o un contenitore di un widget di dimensioni fisse.

Schema di una pagina con intestazione, articolo, widget, articoli correlati e piè di pagina: senza contain, quando il widget cambia il browser verifica tutta la pagina, evidenziata in rosso; con contain layout paint il ricalcolo resta confinato nel widget, evidenziato in giallo
Con il contenimento, una modifica dentro un componente non costringe il browser a verificare il resto della pagina.

I casi d’uso più interessanti sono i componenti che cambiano spesso: un widget che si aggiorna periodicamente, un contatore, un carrello laterale, un blocco di chat, un pannello di filtri che mostra e nasconde opzioni. Senza contenimento, ogni cambiamento al loro interno può costringere il browser a ricalcolare la disposizione di una porzione ampia della pagina. Con contain: content il lavoro resta confinato. È un guadagno che si nota soprattutto sulle interazioni, cioè sull’INP, nelle pagine grandi.

Il contenimento ha effetti collaterali da conoscere. Con paint il contenuto che sporge dai bordi viene tagliato, il che può nascondere menu a tendina, suggerimenti o ombre che escono dal riquadro. Con layout l’elemento diventa il riferimento per gli elementi posizionati al suo interno, il che può cambiare la posizione di elementi in posizione fissa o assoluta. Per questo va applicato componente per componente, verificando il risultato, e non in modo generalizzato.

Container queries: componenti adattivi senza script

Le media queries adattano lo stile alla larghezza della finestra. Le container queries lo adattano alla larghezza del contenitore in cui un componente si trova. La stessa scheda di prodotto può mostrarsi orizzontale nella colonna principale e verticale in una barra laterale stretta, senza bisogno di classi diverse o di codice che misuri lo spazio disponibile.

.griglia-prodotti { container-type: inline-size; }

.scheda { display: grid; gap: 1rem; }

@container ( min-width: 36rem ) {
	.scheda { grid-template-columns: 12rem 1fr; }
}

Le container queries sono soprattutto uno strumento di progettazione, ma hanno un effetto sulle prestazioni: sostituiscono le soluzioni in JavaScript che misuravano i contenitori con ResizeObserver o, peggio, con gestori dell’evento di ridimensionamento, e aggiungevano classi di conseguenza. Quelle soluzioni eseguivano codice sul main thread, e spesso causavano un primo rendering con lo stile sbagliato seguito da una correzione, con uno spostamento visibile. Con le container queries l’adattamento avviene nel calcolo degli stili, al primo rendering.

Da notare che container-type: inline-size applica il contenimento della dimensione orizzontale: la larghezza del contenitore non può dipendere dal suo contenuto. Nella maggior parte dei layout non è un problema, perché i contenitori prendono la larghezza dal genitore, ma un contenitore che doveva adattarsi al contenuto può ridursi inaspettatamente.

CSS che sostituisce JavaScript

Molte funzionalità che richiedevano librerie JavaScript sono oggi disponibili nel CSS o nell’HTML nativo. Ogni script eliminato è codice in meno da scaricare, analizzare ed eseguire, e meno lavoro sul main thread durante le interazioni:

  • caroselli: lo scroll snapping permette di creare caroselli scorrevoli con agganci precisi alle singole schede, senza librerie;
  • fisarmoniche e domande frequenti: l’elemento details con summary apre e chiude contenuti senza script;
  • finestre modali e menu a comparsa: l’elemento dialog e l’attributo popover gestiscono apertura, chiusura, livello di sovrapposizione e tasto Esc;
  • intestazioni fisse: position: sticky sostituisce gli script che ascoltavano lo scorrimento per fissare un elemento;
  • proporzioni: aspect-ratio riserva lo spazio di immagini, video e riquadri incorporati, prevenendo gli spostamenti di layout;
  • stili condizionali: il selettore :has() permette di dare uno stile a un elemento in base al suo contenuto, un’operazione che prima richiedeva script;
  • animazioni allo scorrimento: come visto nell’articolo sulle animazioni performanti, possono essere dichiarate in CSS.

Non tutte queste soluzioni sono perfette sostituzioni: una libreria di caroselli offre funzioni che lo scroll snapping non ha, come la riproduzione automatica. Ma in molti siti quelle funzioni non servono, e la libreria pesa decine di kilobyte per ottenere un effetto che il browser offre già.

E i selettori?

Una domanda frequente riguarda il costo dei selettori complessi. Nella grande maggioranza dei siti è trascurabile: i motori dei browser sono molto ottimizzati, e il tempo speso a valutare i selettori è una frazione minima del rendering. Diventa rilevante solo in casi estremi, con DOM molto grandi, fogli di stile con migliaia di regole e modifiche frequenti. Il selettore :has() merita una piccola attenzione, perché una regola come body:has(.qualcosa) costringe il browser a rivalutare lo stile di una parte ampia della pagina ogni volta che il contenuto cambia; limitarne l’ambito al componente interessato è una buona abitudine. In generale, però, il peso del CSS inutilizzato e la dimensione del DOM contano molto più della forma dei selettori.

Compatibilità e miglioramento progressivo

Le proprietà descritte sono supportate dalle versioni recenti di tutti i browser principali: le container queries e contain da tempo, content-visibility più di recente anche in Firefox e Safari. Il vantaggio di queste proprietà è che i browser che non le conoscono le ignorano: la pagina funziona comunque, semplicemente senza l’ottimizzazione. È un caso ideale di miglioramento progressivo, che non richiede alternative o codice di compatibilità, con l’unica eccezione delle container queries, che cambiano l’aspetto dei componenti e richiedono quindi uno stile di base accettabile anche senza di esse.

In WordPress

In un sito WordPress queste regole si aggiungono nel CSS del tema figlio o, nei temi a blocchi, nel CSS aggiuntivo dell’editor del sito. I bersagli naturali sono le sezioni che il tema genera in fondo alla pagina: la lista dei commenti, i blocchi di articoli correlati, il piè di pagina, i blocchi di query nelle pagine di archivio. È più efficace applicare content-visibility ai singoli elementi di una lista lunga, come i singoli commenti, che all’intera lista, perché il browser può così elaborare solo quelli che si avvicinano allo schermo. Da evitare invece l’applicazione a tutti i blocchi del contenuto dell’articolo, che include quasi sempre l’elemento LCP e le prime sezioni visibili.

Come misurare

Come per ogni ottimizzazione, conviene misurare prima e dopo. Nel pannello Performance dei DevTools, registrando il caricamento della pagina con il rallentamento della CPU, si confronta il tempo speso nelle fasi di ricalcolo degli stili, layout e disegno. Registrando un’interazione, come l’apertura di un menu o l’aggiornamento di un filtro, si verifica se il contenimento riduce il lavoro. Sui dati reali, un intervento efficace si riflette nel tempo di rendering iniziale e nell’INP delle pagine lunghe, più che nei punteggi di laboratorio, che su pagine brevi possono non mostrare differenze.

Un esempio concreto

Consideriamo un blog con articoli molto commentati: alcune pagine hanno oltre trecento commenti, ciascuno con avatar, testo e pulsanti di risposta, seguiti da un blocco di articoli correlati e da un piè di pagina con diversi widget. Su uno smartphone di fascia media il rendering iniziale di queste pagine occupa il main thread per diverse centinaia di millisecondi, e l’apertura del menu di navigazione risponde con ritardo. Applicando content-visibility: auto ai singoli commenti, con una dimensione stimata ragionevole, e al blocco dei correlati, il browser elabora solo i contenuti vicini allo schermo; aggiungendo contain: content al menu, la sua apertura non coinvolge più il resto della pagina. Il contenuto resta identico, e l’intervento consiste in poche righe di CSS.

In sintesi

Il CSS moderno offre strumenti che riducono il lavoro del browser dopo il caricamento. content-visibility: auto rimanda l’elaborazione dei contenuti fuori schermo ed è efficace sulle pagine lunghe, a patto di fornire una dimensione stimata e di non applicarlo ai contenuti visibili al caricamento. contain confina il ricalcolo dentro i componenti che cambiano spesso, con qualche effetto collaterale da verificare. Le container queries adattano i componenti al loro spazio senza script. Molte altre funzionalità native permettono di eliminare librerie JavaScript. Sono interventi piccoli, sicuri grazie al miglioramento progressivo, che vanno applicati dove la pagina è davvero grande e verificati con le misure.

IL PROSSIMO PASSO

Partiamo
dal tuo sito.

Raccontaci il progetto, i problemi che riscontri e i tuoi obiettivi. Definiremo insieme un intervento su misura.

Richiedi un’analisi

Ti porta al modulo della pagina Contatti.

MANAGED SERVER S.R.L.info@corewebvitals.it +39 02 5656 9681

Lunedì – venerdì · 09:30 – 19:30

Preferisci un modulo online?
Contattaci su managedserver.it ↗