Salta al contenuto principale

04 - CLS Prevention: Stabilita Visiva e Layout Shift Debugging

CLS Prevention: Stabilita Visiva e Layout Shift Debugging Stai leggendo un articolo, stai per cliccare su un link - e all'improvviso tutto si sposta di...

Cosa ne pensi di questo articolo?

04 - CLS Prevention: Stabilita Visiva e Layout Shift Debugging
Core Web VitalsFoto:Bibek ghoshPexels

In Breve

CLS Prevention: Stabilita Visiva e Layout Shift Debugging Stai leggendo un articolo, stai per cliccare su un link - e all'improvviso tutto si sposta di...

CLS Prevention: Stabilita Visiva e Layout Shift Debugging

Stai leggendo un articolo, stai per cliccare su un link - e all'improvviso tutto si sposta di 200 pixel verso il basso perche un banner pubblicitario e apparso sopra il testo. Hai appena vissuto un Cumulative Layout Shift. Il CLS e la metrica Core Web Vitals che misura quanto i contenuti di una pagina si spostano in modo inaspettato durante il caricamento e l'utilizzo.

Secondo i dati CrUX di Google, circa il 27% dei siti web ha un CLS "Poor" (superiore a 0.25), il che significa che un utente su quattro sperimenta spostamenti di layout visivamente disturbanti sulle pagine piu visitate. A differenza di INP e LCP, il CLS non riguarda la velocita: riguarda la stabilita visiva, un aspetto della user experience che impatta direttamente sulla frustrazione dell'utente e sulla fiducia nel sito.

Cosa Imparerai

  • Come viene calcolato il punteggio CLS e cosa significa "layout shift"
  • Le soglie Good/Needs Improvement/Poor e il loro impatto SEO
  • Le cinque cause piu comuni di CLS e come eliminarle
  • Come usare Chrome DevTools e PerformanceObserver per debuggare i shift
  • Pattern CSS con aspect-ratio, min-height e content-visibility
  • Come gestire font swap, lazy loading e dynamic content senza CLS

Come Viene Calcolato il CLS

Il CLS non e semplicemente il numero di shift che avvengono: e un punteggio composito che tiene conto sia dell'ampiezza dello shift (impact fraction) sia della distanza percorsa dall'elemento (distance fraction).

La formula e:

layout shift score = impact fraction * distance fraction

L'impact fraction e la percentuale dell'viewport coinvolta nello shift. Se un elemento che occupa il 50% dell'altezza del viewport si sposta di 25% dell'altezza del viewport, la impact fraction e 0.75 (l'area totale impattata prima e dopo lo shift). La distance fraction e la proporzione del viewport percorsa dall'elemento durante lo shift.

Le Soglie di CLS

Rating Valore CLS Esperienza Utente
Good < 0.1 Stabilita visiva eccellente
Needs Improvement 0.1 - 0.25 Qualche spostamento percettibile
Poor > 0.25 Layout instabile, utenti frustrati

Il CLS viene calcolato sommando i punteggi di tutti i layout shift nella pagina, con un'eccezione importante: gli shift causati dall'interazione dell'utente (scroll, click, input) non vengono conteggiati. Solo gli shift inattesi pesano sul punteggio finale.

Le Cinque Cause Principali di CLS

1. Immagini e Media Senza Dimensioni Esplicite

La causa piu frequente di CLS e caricare immagini senza specificare width e height. Quando il browser incontra un tag <img> senza dimensioni, non riserva spazio nel layout. Quando l'immagine si carica, il browser inserisce l'elemento nel DOM e tutto il contenuto sottostante si sposta verso il basso.

Sbagliato:

<img src="hero.jpg" alt="Hero image">

Corretto:

<img src="hero.jpg" alt="Hero image" width="1200" height="630">

Specificare width e height consente al browser di calcolare l'aspect ratio dell'immagine prima del caricamento e riservare lo spazio corretto nel layout. Il CSS moderno poi gestisce il responsive behavior:

img {
  max-width: 100%;
  height: auto; /* mantiene l'aspect ratio */
}

2. Font Web con Fallback Visivamente Diverso

Quando un font web non e ancora scaricato, il browser mostra il testo con un font di sistema (fallback). Quando il font web si carica, il browser rimpiazza il fallback provocando un cambiamento di dimensioni del testo (il cosiddetto "font swap CLS"). La differenza metrica tra un font di sistema e un font web puo essere significativa, causando shift di centinaia di pixel su pagine con molto testo.

La soluzione moderna e usare size-adjust, ascent-override, descent-override e line-gap-override per allineare le metriche del font fallback a quelle del font web:

/* Font fallback ottimizzato per Inter */
@font-face {
  font-family: 'Inter-Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'Inter', 'Inter-Fallback', sans-serif;
}

Questo approccio, combinato con font-display: swap, riduce il CLS da font quasi a zero perche il fallback ha le stesse dimensioni metriche del font finale.

3. Contenuto Dinamico Iniettato Sopra il Contenuto Esistente

Banner cookie, newsletter popup, notifiche di sistema e annunci pubblicitari che appaiono sopra o all'interno del contenuto dopo il caricamento sono tra le cause piu frustranti di CLS. Il pattern problematico e iniettare un elemento nel DOM in una posizione che sposta il contenuto gia visibile.

La regola e semplice: prenota sempre lo spazio prima che il contenuto arrivi:

/* Riservare spazio per un banner ad prima che sia caricato */
.ad-container {
  min-height: 90px;  /* altezza standard banner leaderboard */
  width: 728px;
  max-width: 100%;
  /* Il banner si inserisce senza spostare nulla */
}

/* Cookie banner: posizionamento fisso non impatta il layout */
.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  /* fixed non partecipa al flow del documento */
}

4. Animazioni CSS che Usano Proprieta che Triggherano Layout

Non tutte le animazioni CSS causano layout shift, ma alcune si. Le proprieta top, left, right, bottom, width, height, margin e padding triggherano il reflow del browser (layout) e possono causare CLS se animano elementi che influenzano il layout di altri elementi.

La soluzione e animare solo le proprieta che non triggherano layout: transform e opacity. Queste vengono gestite dal compositor thread senza coinvolgere il main thread.

/* Sbagliato: anima proprieta di layout */
.elemento-animato {
  animation: slide-sbagliato 0.3s ease;
}

@keyframes slide-sbagliato {
  from { left: -100px; }
  to { left: 0; }
}

/* Corretto: usa transform */
.elemento-animato {
  animation: slide-corretto 0.3s ease;
}

@keyframes slide-corretto {
  from { transform: translateX(-100px); }
  to { transform: translateX(0); }
}

5. Elementi Iframe e Widget di Terze Parti Senza Dimensioni

Video YouTube embed, widget Twitter, mappe Google e altri iframe di terze parti causano CLS se non hanno dimensioni esplicite. Il comportamento e identico alle immagini: il browser non sa quanto spazio riservare.

<!-- Sbagliato -->
<iframe src="https://www.youtube.com/embed/..."></iframe>

<!-- Corretto: aspect-ratio moderno -->
<div class="video-container">
  <iframe
    src="https://www.youtube.com/embed/..."
    title="Titolo del video"
    allowfullscreen>
  </iframe>
</div>
.video-container {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
}

.video-container iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

Debuggare il CLS con Chrome DevTools

Performance Panel: Identificare i Layout Shift

Il pannello Performance di Chrome DevTools e lo strumento piu preciso per identificare i layout shift. Segui questi passaggi:

  1. Apri DevTools e vai al tab Performance
  2. Clicca il tasto di registrazione (cerchio rosso)
  3. Ricarica la pagina o naviga nella sezione che vuoi analizzare
  4. Ferma la registrazione dopo 5-10 secondi
  5. Nella timeline, cerca le barre viola etichettate Layout Shift
  6. Clicca su un evento Layout Shift per vedere nella sezione Summary il punteggio e gli elementi coinvolti

Rendering Panel: Layout Shift Regions

Un modo alternativo e usare il pannello Rendering. Apri DevTools, premi Escape per aprire il drawer, seleziona il tab "Rendering" e attiva l'opzione "Layout Shift Regions". Il browser evidenziera in blu ogni area che subisce uno shift mentre navighi la pagina.

PerformanceObserver per il Monitoring in Produzione

Per raccogliere dati CLS reali dagli utenti in produzione, usa la PerformanceObserver API o la libreria web-vitals:

import { onCLS } from 'web-vitals';

onCLS((metric) => {
  console.log('CLS finale:', metric.value, metric.rating);

  // Le singole shift entries per il debugging
  metric.entries.forEach((entry) => {
    console.log('Shift:', {
      score: entry.value,
      elements: entry.sources?.map((s) => s.node),
      startTime: entry.startTime,
    });
  });

  // Invia a analytics
  fetch('/api/vitals', {
    method: 'POST',
    body: JSON.stringify({
      metric: 'CLS',
      value: metric.value,
      rating: metric.rating,
    }),
  });
});

Pattern CSS Avanzati per Eliminare il CLS

aspect-ratio: Il Sostituto Moderno del Padding-Bottom Hack

Prima di aspect-ratio, il modo per preservare il rapporto di aspetto di un contenitore era il "padding-bottom hack". Oggi aspect-ratio e disponibile in tutti i browser moderni (Baseline 2021) e offre una soluzione pulita e leggibile:

/* Vecchio approccio: padding-bottom hack */
.video-wrapper-old {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 */
  height: 0;
}

/* Approccio moderno: aspect-ratio */
.video-wrapper {
  aspect-ratio: 16 / 9;
  width: 100%;
}

/* Funziona anche per card immagini */
.card-thumbnail {
  aspect-ratio: 3 / 2;
  overflow: hidden;
}

.card-thumbnail img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

content-visibility: lazy per Sezioni Off-Screen

La proprieta CSS content-visibility: auto istruisce il browser a saltare il rendering di sezioni che non sono nel viewport. Combinata con contain-intrinsic-size, permette al browser di stimare le dimensioni dell'elemento anche prima di renderizzarlo, evitando shift quando la sezione entra nel viewport:

.section-offscreen {
  content-visibility: auto;
  /* Altezza stimata della sezione */
  contain-intrinsic-size: 0 800px;
}

Skeleton Screen: Riserva lo Spazio per Contenuto Asincrono

Per contenuti caricati asincronamente (dati da API, commenti, recommandation widgets), usa uno skeleton screen che replica la struttura del contenuto finale:

/* Skeleton base */
.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.skeleton-title {
  height: 24px;
  width: 70%;
  margin-bottom: 8px;
}

.skeleton-text {
  height: 16px;
  width: 100%;
  margin-bottom: 6px;
}

.skeleton-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
}

CLS nei Framework: Angular, React, Vue

Angular: Gestire il CLS durante l'Hydration

Con Angular SSR, la fase di hydration (quando Angular "prende vita" nell'HTML server-rendered) puo causare shift se i componenti modificano il DOM in modo che crea differenze tra l'HTML server-side e quello che Angular produce client-side. La soluzione e assicurarsi che ngSkipHydration venga usato solo per componenti che davvero non possono fare hydration, e che tutti i componenti che mostrano contenuto above-the-fold abbiano dimensioni fisse.

Lazy Loading Immagini: fetchpriority="high" per LCP + loading="lazy" per il Resto

Un pattern comune che causa CLS involontario e applicare loading="lazy" a tutte le immagini, inclusa quella LCP. Il lazy loading ritarda il caricamento dell'immagine, ma il browser deve comunque riservare spazio - se non ci sono dimensioni esplicite, si ha CLS quando l'immagine viene finalmente caricata.

<!-- Immagine hero (above the fold): non usare lazy -->
<img
  src="hero.webp"
  alt="Hero image"
  width="1200"
  height="600"
  fetchpriority="high"
>

<!-- Immagini below the fold: lazy loading + dimensioni esplicite -->
<img
  src="product-1.webp"
  alt="Prodotto 1"
  width="400"
  height="300"
  loading="lazy"
>

CLS e Single Page Applications

Nelle SPA (React, Angular, Vue), la navigazione client-side puo introdurre CLS se il nuovo "pagina" ha elementi con dimensioni diverse da quelli precedenti. La View Transitions API (disponibile in Chrome e Firefox) offre una soluzione elegante animando il cambio di stato invece di fare un hard replace del DOM. Vedi l'articolo dedicato nella serie per l'implementazione completa.

CLS Audit: Checklist Pratica

Usa questa checklist per auditare il CLS del tuo sito:

  • Tutte le immagini hanno width e height espliciti?
  • Gli iframe e i video embed hanno dimensioni fisse o usano aspect-ratio?
  • I font web usano font-display: swap con metriche fallback ottimizzate?
  • I banner pubblicitari hanno un contenitore con dimensioni minime riservate?
  • Le animazioni usano transform e opacity invece di proprieta di layout?
  • Il contenuto dinamico (da API, commenti) usa skeleton screen o placeholder?
  • I popup e i cookie banner usano position: fixed o position: absolute?
  • Le web font critiche vengono preloaded nella <head>?

Misurare il CLS su Scala: Lighthouse CI e CrUX

Per monitorare il CLS in modo sistematico su un sito con molte pagine, integra Lighthouse CI nel tuo pipeline di deployment:

# lighthouserc.json
{
  "ci": {
    "assert": {
      "assertions": {
        "cumulative-layout-shift": ["error", {"minScore": 0.9}]
      }
    },
    "collect": {
      "url": [
        "http://localhost:4200",
        "http://localhost:4200/products",
        "http://localhost:4200/blog"
      ],
      "numberOfRuns": 3
    }
  }
}
# GitHub Actions: esegui Lighthouse CI su ogni PR
- name: Run Lighthouse CI
  uses: treosh/lighthouse-ci-action@v11
  with:
    configPath: ./lighthouserc.json
    temporaryPublicStorage: true

Next Steps

Con INP, LCP e CLS coperti, hai ora una comprensione completa delle tre metriche Core Web Vitals di Google. Il passo successivo e approfondire l'ottimizzazione dei font: il prossimo articolo della serie esplora font subsetting, variable fonts e le strategie font-display per massimizzare le performance LCP e minimizzare il CLS da font swap.

Conclusioni

Il CLS e una metrica che richiede attenzione sistematica durante tutto il processo di sviluppo, non solo come fix post-hoc. I pattern vincenti sono semplici e consistenti: dimensioni esplicite per ogni risorsa visiva, spazio riservato per contenuto dinamico, animazioni che non triggherano layout, font fallback con metriche allineate.

La buona notizia e che risolvere il CLS e spesso piu facile di risolvere INP o LCP: non richiede refactoring architetturale, ma solo l'applicazione disciplinata di questi pattern CSS e HTML. Un audit metodico con Chrome DevTools e la libreria web-vitals ti permettera di portare il CLS del tuo sito nel range "Good" in una o due sessioni di lavoro.

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti