Errori e fix

Layout shift da immagini senza dimensioni: la correzione che non costa nulla

Un'immagine senza larghezza e altezza occupa spazio zero finché non carica. Il browser sposta tutto il resto e il CLS sale. Due attributi e un `aspect-ratio` risolvono la maggior parte dei casi.

In sintesi

  • Ogni immagine deve dichiarare `width` e `height`: il browser riserva lo spazio prima del download.
  • Per le immagini fluide aggiungi `height: auto` e, se il rapporto cambia, `aspect-ratio`.
  • CLS sotto 0,1 è la soglia di riferimento; il valore di laboratorio non coincide sempre con quello reale.

Risposta breve: dichiara width e height su ogni immagine. Il browser riserva lo spazio prima del download e il resto della pagina non salta quando l'immagine arriva.

Il meccanismo: perché lo spazio non c'è

Il CLS somma il punteggio di ogni spostamento inatteso: la frazione di viewport interessata moltiplicata per la distanza percorsa. Un'immagine senza dimensioni dichiarate parte da altezza zero; quando il file arriva il browser la inserisce, il contenitore si allunga e tutto ciò che sta sotto si sposta. Con width e height il motore ricava il rapporto prima della richiesta di rete e prenota un riquadro della stessa forma: il download riempie uno spazio già riservato, quindi non c'è spostamento. Il meccanismo sta nell'HTML, non nel CSS: i due attributi sono suggerimenti presentazionali che il browser traduce in un aspect-ratio implicito.

Il passo diagnostico che evita la caccia al colpevole

Apri DevTools, carica la pagina con il throttling «Slow 3G» e leggi i singoli spostamenti, non il punteggio finale. Il frammento qui sotto stampa il valore di ogni shift e il nodo che lo ha causato: se il nodo è un img senza attributi, la correzione è quella in cima a questa pagina; se è un div, il problema è un contenitore che cresce dopo il caricamento e gli attributi sull'immagine non serviranno. Leggere il colpevole prima di toccare il markup trasforma un sospetto in una decisione.

// incolla in DevTools ▸ Console, con la pagina già caricata
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      console.log(entry.value.toFixed(4), entry.sources?.[0]?.node?.outerHTML?.slice(0, 80));
    }
  }
}).observe({ type: "layout-shift", buffered: true });

La correzione minima

I due attributi danno al browser il rapporto d'aspetto: lo spazio viene riservato prima del download. height: auto lascia che il CSS ridimensioni in modo fluido senza deformare.

Con un rapporto diverso su mobile

.cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

@media (min-width: 768px) {
  .cover { aspect-ratio: 21 / 9; }
}

aspect-ratio riserva lo spazio anche quando il rapporto cambia con il breakpoint. Senza, il contenitore si adatta all'immagine e il testo sotto si sposta.

Le altre fonti di shift

ElementoPerché spostaCorrezione
font weble metriche cambiano al caricamento`size-adjust` sul fallback
annuncio o embedaltezza sconosciutacontenitore con altezza minima
banner cookiecompare sopra il contenutospazio riservato o overlay
immagine lazynessuna dimensione dichiaratawidth, height, aspect-ratio

Come si misura

npx lighthouse https://example.com --only-categories=performance --view
# oppure: DevTools ▸ Performance ▸ Experience

Lighthouse dà il dato di laboratorio. Per il dato reale serve la Web Vitals API, che misura gli utenti: un CLS basso in laboratorio con rete veloce può essere alto su un telefono lento.

Quando la correzione non funziona

Se il CLS resta alto, il responsabile può non essere l'immagine: cerca elementi che cambiano dimensioni dopo un'interazione. Le immagini senza dimensioni spostano al caricamento; i componenti che si espandono spostano al click. Il pannello Performance mostra quale nodo ha generato lo shift.

Quando la correzione comune peggiora le cose

La scorciatoia diffusa è un'altezza fissa sul contenitore: height: 320px riserva spazio senza toccare il markup. Funziona su un viewport e mente su tutti gli altri. Su uno schermo stretto un'immagine 16:9 è più bassa del riquadro e resta una fascia vuota; su uno schermo largo il riquadro è più basso dell'immagine, che viene tagliata o trabocca. Il CLS scende a zero e la pagina peggiora, così la correzione viene annullata al primo reclamo sul layout. Il rapporto, non l'altezza, è il dato che il browser deve conoscere.

Un limite con un nome: l'art direction

Con <picture> e due <source> che servono un ritaglio quadrato su mobile e uno panoramico su desktop, il rapporto riservato resta quello dichiarato sull'img: il browser sceglie la sorgente dopo, quando ha già prenotato lo spazio. Se il ritaglio mobile ha proporzioni diverse, lo shift torna al primo caricamento da telefono. Il limite si chiama art direction e nessun attributo lo risolve: serve un aspect-ratio per breakpoint sul contenitore, come nel blocco CSS qui sopra. È il caso in cui la correzione minima non basta, e conviene dirlo prima di aprire il ticket.

Come prevenirlo

  • Un componente immagine unico, che richiede width e height come props obbligatorie.
  • Test su rete lenta: gli shift sono più visibili con throttling 3G.
  • Non caricare immagini sopra la piega in lazy loading: ritardi il paint senza guadagnare nulla.

Questi tre punti restano regole sociali finché non diventano un controllo automatico. Lighthouse CI con un budget su cumulative-layout-shift trasforma l'accordo in un blocco di merge; senza budget la prossima immagine aggiunta di fretta riporta il problema e nessuno se ne accorge, perché il CLS non produce errori.

Dove continuare

Nel catalogo: blocco hero, componenti e blocchi. Le guide: peso di una libreria UI e checklist accessibilità; il confronto: copy-paste o headless.

Domande frequenti

Non basta `max-width: 100%`?

No. Il CSS fluido non riserva lo spazio prima del caricamento: il browser non conosce le proporzioni finché non arriva il file.

Perché `width` e `height` insieme a `height: auto`?

Gli attributi danno il rapporto al browser, `height: auto` lascia che il CSS ridimensioni in modo fluido.

E per le immagini con rapporti diversi?

Serve `aspect-ratio` sul contenitore o sull'immagine, altrimenti lo spazio riservato non corrisponde all'immagine finale.

Il CLS include i font?

Sì. Un font che cambia metriche al caricamento produce shift: `font-display: swap` con metriche di fallback adattate riduce il problema.

Come misuro?

Con Lighthouse per il dato di laboratorio e con la Web Vitals API per quello reale. Devono essere letti insieme, non al posto l'uno dell'altro.