Go to Top

Cumulative Layout Shift (CLS) – Core Web Vitals Parte 1

Si chiama Cumulative Layout Shift ed è uno dei tre Core Web Vitals che sono fattore di ranking su Google dal 2021.

Che cos’è il CLS, Cumulative Layout Shift

Il Cumulative Layout Shift (CLS) è una metrica di Google che misura la stabilità visiva attraverso l’analisi di un evento dell’esperienza utente,
ovvero lo spostamento imprevisto degli elementi della pagina Web mentre la stessa è ancora in fase di caricamento, si tratta quindi di garantire una user experience positiva.

Al contrario degli altri due Core Web VitalsLargest Contentful Paint e First Input Delay – la sua unità di misura di riferimento non è il tempo, ma lo spazio e per misurarlo Google ha creato un punteggio specifico per identificare il livello delle variazioni di layout nel corso del caricamento della pagina che possono compromettere la user experience.

I problemi con le variazioni di layout
Il CLS si verifica quando il contenuto della pagina si sposta senza l’input volontario dell’utente o una notifica preventiva: gli elementi onpage che tendono a causare le variazioni sono:

  • caratteri,
  • immagini,
  • video,
  • moduli di contatto,
  • pulsanti e altri tipi di contenuti

Nel caso più comune, l’utente che sta visitando la pagina è pronto a cliccare su un elemento, ma nel frattempo il caricamento ne modifica la posizione e porta la persona a “sbagliare” e cliccare su qualche altra cosa come un annuncio o una finestra pop-up.

Le cause del Cumulative Layout Shift

Secondo i Googler ci sono cinque motivi per cui si verifica una variazione cumulativa del layout:

  • Immagini o video di dimensioni sconosciute / non impostate correttamente
  • Annunci, embeds e iframe senza dimensioni
  • Web font che causano FOIT (Flash of Invisible Text) / FOUT (Flash of Unstyled Text)
  • Animazioni e Azioni in attesa di una risposta di rete prima di aggiornare DOM
  • Contenuto Dinamico

Immagini senza dimensioni

Le immagini senza dimensioni costituiscono un problema perché il browser non può allocare sin dal principio la corretta quantità di spazio nel documento durante il caricamento dell’immagine. Quando le immagini senza dimensioni appaiono sullo schermo la pagina scorre e il testo si sposta e questo compromette la user experience.

An example layout with a title and two paragraphs, where the second paragraph has to shift down to make space for an image.

La soluzione semplice per risolvere questo problema è includere gli attributi di larghezza e altezza sulle tue immagini e sugli elementi video, in questo modo il browser si allocherà lo spazio necessario anche prima che l’immagine sia stata scaricata dal server senza causare lo shift del layout.

An example layout mockup with a title, a paragraph, space for an image and then a second paragraph, where the text does not shift when the image loads.

In passato gli sviluppatori aggiungevano sempre gli attributi di larghezza e altezza ai loro tag <img> per garantire che sulla pagina fosse allocato spazio sufficiente prima che il browser iniziasse a recuperare le immagini e ciò riduceva al minimo il reflow e il re-layout della pagina (es di stili css che causano reflow o re-layout https://csstriggers.com/).
Con il Responsive Web Design, gli sviluppatori hanno iniziato a omettere larghezza e altezza e hanno iniziato a utilizzare CSS per ridimensionare le immagini per far modo che si adattassero allo spazio disponibile:

img {
   width: 100%; /* o max-width: 100%; */
   height: auto;
}

In questo modo però il risultato torna ad essere questo:

Another example layout mockup with a title and two paragraphs, where the second paragraph has to shift down to make space for an image.

Questo perché il browser per poter allocare l’altezza dell’immagine ha bisogno di conoscere le dimensioni dell’immagine e poi alloca lo spazio solo dopo aver scaricato l’immagine e calcolato il width a disposizione.

Come workaround storicamente è stato usato il padding-bottom hack che consiste nel creare un wrapper con un aspect-ratio predeterminato per un certo elemento, in questo caso per un’immagine in questo modo:

.img-container {
   position: relative;
   padding-bottom: 56.25%; /* 16:9 ratio */
   height: 0;
   overflow: hidden;
}

.img-container img {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
}

Ma questo hack comporta il dover creare molti container (uno per ogni immagine) ed è poco pratico e soggetto ad errori.

È qui che entrano in gioco l’aspect-ratio ovvero il rapporto tra la larghezza e altezza dell’immagine. In questo modo possiamo suggerire al browser qual’è l’altezza dell’immagine prima che venga scaricata e sarà tale da mantenere l’aspect-ratio fissato dato il width disponibile.
img {
   width: 100%;
   height: auto;
   aspect-ratio: 16/9;
}

Una soluzione definitiva è stata proposta e sarebbe possibile usando la funzione attr(width) e attr(height) nel css per ottenere l’aspect-ratio in questo modo  aspect-ratio:attr(width)/attr(height)
applicando questo singolo css alle immagini direttamente nello User Agent Styleseet non ci sarebbe neanche bisogno di aggiungere codice custom per evitare il layout shift.
img {
   aspect-ratio: attr(width) / attr(height);
}
Al momento però la funzione attr è limitata al solo content mentre per le altre proprietà è ancora sperimentale.
https://caniuse.com/mdn-css_properties_aspect-ratio

In ogni modo la soluzione precedente:
aspect-ratio: 16/9;
è funzionale e applicabile anche se con alcune precisazioni:

  1. Art Direction
  2. Lazy Loading
  3. Non-Images

1. Art Direction

Nel tag picture abbiamo diversi source per dare immagini diverse in base al device, fino ad Chrome 90 non si poteva specificare gli attributi width e height per ogni source ma solo per il fallback e quindi l’aspect ratio può non essere lo stesso per le varie versioni dell’immagine pertanto l’utilizzo di aspect-ratio non risolve il problema dello shift
<picture>
   <source srcset=”https://via.placeholder.com/600×279″ media=”(max-width: 599px)”>
   <source srcset=”https://via.placeholder.com/1500×300″ media=”(max-width: 991px)”>
   <img src=”https://via.placeholder.com/1500×300″ width=”1500″ height=”300″>
</picture>

da chrome 90 invece è possibile specificarlo
<source srcset=”https://via.placeholder.com/600×279″ media=”(max-width: 599px)” width=”600″ height=”279″>

2. Lazy Loading

Le immagini caricate in Lazy Loading solitamente vengono caricate off-screen quando l’utente fa scroll, teoricamente non ci sarebbe problema, ma in caso di connessioni lente lo shift potrebbe ancora avvenire, pertanto, visto che la tecnica dell’aspect-ratio non può essere applicata per lazy loading effettuate con librerie che prevedono di non impostare l’src, per poter beneficiare dell’aspect-ratio il lazy loading deve essere implementato con la nuova proprietà nativa css:
<img src=”hero_800x400.jpg” alt=”” width=”800″ height=”400″ loading=”lazy”>
https://caniuse.com/loading-lazy-attr

3. Non-images

L’aspect-ratio è stato implementato per img ma inzialmente non per altri elementi HTML, Chrome 88, FireFox 89 e Safari 15 viene supportato
https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio#browser_compatibility
https://drafts.csswg.org/css-sizing-4/#aspect-ratio

Pubblicità, embeds, iframe

Ads sono frequenti nei siti di informazione ma possono introdurre uno spostamento del layout.
Le cose da fare per evitare layout shift dovuti agli ads sono:
Fai attenzione quando inserisci annunci nella parte superiore della finestra (parte above the fold), è preferibile mettere gli annunci in basso o in alternativa, se l’annuncio va posizionato in alto, riservare staticamente spazio per l’area annuncio riservando la dimensione più grande possibile per l’area annuncio.
Evita di comprimere lo spazio riservato se non viene restituito alcun annuncio quando l’area annuncio è visibile mostrando un placeholder (reperibile ad esempio al sito https://placeholder.com/).

Embed e widget permettono di includere contenuto dinamico, nella pagina, i più comuni sono i video da youtube, le mappe da google map, gli ultimi post dai social.
Questi embed possono essere realizzati in diversi modi:

  • tramite html contenitori più javascript che trasforma il contenitore,
  • in html,
  • con iframe

Tutte queste tecniche hanno a comune che non sanno a priori quanto spazio occuperanno (es social media post).
La soluzione in questi casi sta nell’ispezionare gli embed una volta che sono stati renderizzati, calcolare lo spazio occupato e utilizzare un placeholder per allocare quello spazio con le varianti responsive del caso.

 

Font Web

I Web-Font sono un’altra causa comune di CLS a causa del browser che inizialmente calcola lo spazio necessario in base al carattere di fallback e quindi lo ricalcola quando viene scaricato il carattere Web.
Di solito il CLS è piccolo a condizione che venga utilizzato un font di fallback di dimensioni simili, quindi spesso non causano grandi problemi di CLS ma possono comunque essere fastidiosi per gli utenti.

Ci sono 2 tipi di layout shift causati da Web Font:
FOUT (flash of untiled text) quando il font di fallback viene sostituito con il font web fintanto che quest’ultimo non viene scaricato
FOIN (flash of invisible text) quando il testo è nascosto fintanto che il font non viene caricato

La proprietà CSS font-display con valori come auto, swap, block, fallback e optional può aiutare a modificare il comportamento di rendering del font ma comporta, eccetto che per optional, un re-layout della pagina.

Le tecniche per minimizzare se non addirittura azzerare questo fenomeno lavorano sulle:

  1. Minimizzare lo shift che avviene quando il font di fallback viene sostituito dal web font nel FOUT
  2. Lavorando sulle tempistiche di caricamento dei font

1. Minimizzare lo shift

Per minimizzare lo shift  ci sono i nuovi CSS Font Descriptor che consentono di regolare più facilmente i caratteri di fallback nei CSS impostando gli aggiustamenti dei fallback es:
@font-face {
   font-family: ‘Lato’;
   src: url(‘/static/fonts/Lato.woff2’) format(‘woff2’);
   font-weight: 400;
}

@font-face {
   font-family: ‘Lato-fallback’;
   size-adjust: 97.38%;
   ascent-override: 99%;
   src: local(“Arial”);
}

h1 {
   font-family: Lato, Lato-fallback, sans-serif;
}

2. Lavorare sui tempi di caricamento

Per ridurre i tempi di caricamento innanzitutto è consigliabile scaricare solamente gli style e i weight che effettivamente utilizzeremo, inoltre usando facendo preload (abbinato all’utilizzo di font-display optional) è possibile anticipare il caricamento del font che altrimenti (ad esempio se utilizziamo @font-face) avverrebbe in lazy-load dopo le altre risorse critiche (CSS/JS)…
<link rel=”preload” href=”/assets/Pacifico-Bold.woff2″ as=”font” type=”font/woff2″ crossorigin>
E’ possibile anticipare ulteriormente il preload usando gli HEADER a livello WEBSERVER, in questo modo andremo a pushare la risorsa insieme al suo HTML…
Header add Link “</assets/Pacifico-Bold.woff2>; rel=preload; as=font; type=font/woff2; crossorigin”

Animazioni e Azioni in attesa di una risposta di rete prima di aggiornare DOM

Le interazioni dell’utente che causano lo spostamento del contenuto entro 500 ms dopo l’interazione sono escluse dai punteggi CLS.
Quindi, se fai un’animazione o fai clic su un pulsante a cui segue un’elaborazione complessa che richiede oltre 500 ms che provoca rendering di alcuni nuovi contenuti, il tuo punteggio CLS ne risentirà.
Assicurati quindi che le interazioni dell’utente vengano completate entro 500 ms.
Inoltre sono da preferire le trasformazioni alle animazioni di proprietà css in quanto queste ultime comportano un re-layout.
Una lista delle Proprietà che causano un re-layout è disponibile qui.

Contenuto Dinamico

Per quanto riguarda i vari contenuti dinamici come cookie banner o iscriviti alla nostra newsletter, anche per loro la soluzione consiste nel collocarli a fondo pagina o in caso debbano essere messi above-the-fold nell’allocare in anticipo lo spazio che richiederanno.
Nel caso invece di contenuti dinamici caricati tramite Ajax è consigliabile  far si che sia l’utente ad interagire con la pagina per caricarli come ad esempio il caricamento Ajax della paginazione dei prodotti nei listing tramite bottone “Load more”.
In caso invece di aggiornamenti Ajax di contenuti preesistenti nella pagina che non siano conseguenti ad un’azione dell’utente in UI sarà necessario riutilizzare lo spazio precedentemente allocato.

https://web.dev/optimize-cls/
https://www.smashingmagazine.com/2021/06/how-to-fix-cumulative-layout-shift-issues/
https://www.smashingmagazine.com/2020/03/setting-height-width-images-important-again/

FOIT vs FOUT: a comparison on web font loading