Creazione di un componente immagine efficace

Un componente immagine racchiude le best practice relative al rendimento e fornisce una soluzione pronta all'uso per ottimizzare le immagini.

Leena Sohoni
Leena Sohoni
Kara Erickson
Kara Erickson
Alex Castle
Alex Castle

Le immagini sono una fonte comune di colli di bottiglia delle prestazioni per le applicazioni web e un'area di interesse chiave per l'ottimizzazione. Le immagini non ottimizzate contribuiscono al gonfiore delle pagine e attualmente rappresentano oltre il 70% del peso totale delle pagine in byte al 90° percentile. Esistono diversi modi per ottimizzare le immagini che richiedono un "componente immagine" intelligente con soluzioni per le prestazioni integrate come impostazione predefinita.

Il team di Aurora ha collaborato con Next.js per creare uno di questi componenti. L'obiettivo era creare un modello di immagine ottimizzato che gli sviluppatori web potevano personalizzare ulteriormente. Il componente funge da buon modello e definisce uno standard per la creazione di componenti di immagini in altri framework, sistemi di gestione dei contenuti (CMS) e stack tecnici. Abbiamo collaborato a un componente simile per Nuxt.js e stiamo lavorando con Angular all'ottimizzazione delle immagini nelle versioni future. Questo post illustra come abbiamo progettato il componente Immagine Next.js e ciò che abbiamo imparato man mano.

Componente immagine come estensione delle immagini

Opportunità e problemi relativi all'ottimizzazione delle immagini

Le immagini non influiscono solo sul rendimento, ma anche sull'attività. Il numero di immagini su una pagina è stato il secondo maggiore fattore di previsione delle conversioni degli utenti che visitavano i siti web. Le sessioni in cui gli utenti hanno effettuato la conversione avevano il 38% di immagini in meno rispetto alle sessioni in cui non hanno effettuato la conversione. Lighthouse elenca diverse opportunità per ottimizzare le immagini e migliorare i Web Vitals nell'ambito del proprio controllo delle best practice. Di seguito sono riportate alcune aree comuni in cui le immagini possono influire sui Core Web Vitals e sull'esperienza utente.

Le immagini non ridimensionate danneggiano la metrica CLS

Le immagini pubblicate senza le dimensioni specificate possono causare instabilità del layout e contribuire a un elevato Cumulative Layout Shift (CLS). L'impostazione degli attributi width e height negli elementi img può aiutare a evitare variazioni del layout. Ad esempio:

<img src="flower.jpg" width="360" height="240">

La larghezza e l'altezza devono essere impostate in modo che le proporzioni dell'immagine visualizzata siano simili alle proporzioni naturali. Una differenza significativa nelle proporzioni può rendere l'immagine distorta. Una proprietà relativamente nuova che consente di specificare aspect-ratio in CSS può aiutare a ridimensionare le immagini in modo reattivo, evitando al contempo la metrica CLS.

Le immagini di grandi dimensioni possono danneggiare l'LCP

Maggiore è la dimensione del file di un'immagine, maggiore sarà il tempo necessario per il download. Un'immagine di grandi dimensioni potrebbe essere l'immagine "hero" della pagina o l'elemento più significativo dell'area visibile responsabile dell'attivazione della metrica LCP (Largest Contentful Paint). Un'immagine che fa parte dei contenuti critici e il cui download richiede molto tempo ritarda la visualizzazione dell'LCP.

In molti casi, gli sviluppatori possono ridurre le dimensioni delle immagini migliorando la compressione e l'utilizzo di immagini reattive. Gli attributi srcset e sizes dell'elemento <img> consentono di fornire file immagine di dimensioni diverse. Il browser può quindi scegliere quello giusto in base alle dimensioni e alla risoluzione dello schermo.

Una compressione delle immagini di scarsa qualità può danneggiare la metrica LCP

I formati delle immagini moderni, come AVIF o WebP, possono fornire una compressione migliore rispetto ai formati JPEG e PNG di uso comune. Una compressione migliore riduce le dimensioni del file dal 25% al 50% in alcuni casi mantenendo la stessa qualità dell'immagine. Questa riduzione si traduce in download più veloci con un consumo di dati minore. L'app deve pubblicare formati delle immagini moderni sui browser che supportano questi formati.

Il caricamento di immagini non necessarie danneggia l'LCP

Le immagini below the fold o non nell'area visibile non vengono mostrate all'utente quando la pagina viene caricata. Possono essere differiti in modo da non contribuire all'LCP e ritardarlo. Il caricamento lento può essere utilizzato per caricare queste immagini in un secondo momento mentre l'utente scorre verso le immagini.

Sfide di ottimizzazione

I team possono valutare il costo del rendimento dovuto ai problemi elencati in precedenza e implementare soluzioni di best practice per risolverli. Tuttavia, spesso questo non accade nella pratica e le immagini inefficienti continuano a rallentare il web. Di seguito sono elencati alcuni possibili motivi:

  • Priorità: in genere gli sviluppatori web tendono a concentrarsi su codice, JavaScript e ottimizzazione dei dati. Di conseguenza, potrebbero non essere a conoscenza dei problemi con le immagini o di come ottimizzarle. Le immagini create da designer o caricate dagli utenti potrebbero non essere in cima all'elenco delle priorità.
  • Soluzione pronta all'uso: anche se gli sviluppatori sono consapevoli delle sfumature dell'ottimizzazione delle immagini, l'assenza di una soluzione pronta all'uso all-in-one per il framework o lo stack tecnico può essere un deterrente.
  • Immagini dinamiche: oltre alle immagini statiche che fanno parte dell'applicazione, le immagini dinamiche vengono caricate dagli utenti o provenienti da database esterni o CMS. Può essere difficile definire le dimensioni di queste immagini se la loro origine è dinamica.
  • Sovraccarico di markup: le soluzioni per includere le dimensioni dell'immagine o srcset per dimensioni diverse richiedono un markup aggiuntivo per ogni immagine, il che può essere noioso. L'attributo srcset è stato introdotto nel 2014 ma è utilizzato solo dal 26,5% dei siti web oggi. Quando utilizzano srcset, gli sviluppatori devono creare immagini di varie dimensioni. Strumenti come just-gimme-an-img possono essere utili, ma devono essere utilizzati manualmente per ogni immagine.
  • Supporto dei browser: i moderni formati di immagine come AVIF e WebP creano file immagine più piccoli, ma richiedono una gestione speciale sui browser che non li supportano. Gli sviluppatori devono utilizzare strategie come la negoziazione dei contenuti o l'elemento <picture> affinché le immagini vengano pubblicate su tutti i browser.
  • Complicazioni per il caricamento lento: sono disponibili diverse tecniche e librerie per implementare il caricamento lento per le immagini below the fold. Scegliere il modello migliore può essere difficile. Gli sviluppatori potrebbero anche non conoscere la distanza migliore dal punto di piegatura per caricare le immagini differite. Ciò può complicare ulteriormente l'area visibile di dimensioni diverse sui dispositivi.
  • Cambio orizzontale: man mano che i browser iniziano a supportare nuove funzionalità HTML o CSS per migliorare il rendimento, potrebbe essere difficile per gli sviluppatori valutarle ciascuna. Ad esempio, Chrome sta introducendo la funzionalità Priorità di recupero come Prova dell'origine. Può essere utilizzato per aumentare la priorità di immagini specifiche nella pagina. In generale, per gli sviluppatori sarebbe più facile se questi miglioramenti fossero valutati e implementati a livello di componente.

Componente immagine come soluzione

Le opportunità disponibili per ottimizzare le immagini e le difficoltà legate alla loro implementazione singolarmente per ogni applicazione ci hanno portato all'idea di un componente delle immagini. Un componente immagine può incapsulare e applicare le best practice. Sostituendo l'elemento <img> con un componente immagine, gli sviluppatori possono risolvere meglio i problemi relativi alle prestazioni delle immagini.

Nell'ultimo anno, abbiamo collaborato con il framework Next.js per progettare e implement il componente Immagine. Può essere utilizzato come sostituzione diretta per gli elementi <img> esistenti nelle app Next.js, come indicato di seguito.

// Before with <img> element:
function Logo() {
  return <img src="/logo.jpg" alt="logo" height="200" width="100" />
}

// After with image component:
import Image from 'next/image'

function Logo() {
  return <Image src="/logo.jpg" alt="logo" height="200" width="100" />
}

Il componente cerca di risolvere i problemi relativi alle immagini in modo generico attraverso un'ampia gamma di funzionalità e principi. Include inoltre opzioni che consentono agli sviluppatori di personalizzarla per vari requisiti delle immagini.

Protezione dalle variazioni del layout

Come già detto, le immagini senza dimensioni causano variazioni del layout e contribuiscono alla metrica CLS. Quando utilizzano il componente Immagine Next.js, gli sviluppatori devono fornire una dimensione delle immagini utilizzando gli attributi width e height per evitare eventuali variazioni del layout. Se la dimensione non è nota, gli sviluppatori devono specificare layout=fill per pubblicare un'immagine senza dimensioni che si trovi all'interno di un contenitore di dimensioni. In alternativa, puoi utilizzare le importazioni di immagini statiche per recuperare le dimensioni dell'immagine effettiva sul disco rigido al momento della creazione e includerle nell'immagine.

// Image component with width and height specified
<Image src="/logo.jpg" alt="logo" height="200" width="100" />

// Image component with layout specified
<Image src="/hero.jpg" layout="fill" objectFit="cover" alt="hero" />

// Image component with image import
import Image from 'next/image'
import logo from './logo.png'

function Logo() {
  return <Image src={logo} alt="logo" />
}

Poiché gli sviluppatori non possono utilizzare le dimensioni del componente Immagine senza dimensioni, il design fa sì che prendano il tempo necessario per valutare le dimensioni delle immagini e prevenga le variazioni del layout.

Favorisci la reattività

Per rendere adattabili le immagini su più dispositivi, gli sviluppatori devono impostare gli attributi srcset e sizes nell'elemento <img>. L'obiettivo è ridurre questo impegno con il componente Immagine. Abbiamo progettato il componente Immagine Next.js in modo da impostare i valori degli attributi solo una volta per applicazione. Le applichiamo a tutte le istanze del componente Immagine in base alla modalità di layout. Abbiamo ideato una soluzione in tre parti:

  1. Proprietà deviceSizes: questa proprietà può essere usata per configurare i punti di interruzione una volta in base ai dispositivi comuni alla base utenti dell'applicazione. I valori predefiniti per i punti di interruzione sono inclusi nel file di configurazione.
  2. Proprietà imageSizes: è anche una proprietà configurabile utilizzata per ottenere le dimensioni delle immagini corrispondenti ai punti di interruzione delle dimensioni del dispositivo.
  3. Attributo layout in ogni immagine: viene utilizzato per indicare come utilizzare le proprietà deviceSizes e imageSizes per ogni immagine. I valori supportati per la modalità layout sono fixed, fill, intrinsic e responsive

Quando viene richiesta un'immagine con le modalità di layout adattabile o completa, Next.js identifica l'immagine da pubblicare in base alle dimensioni del dispositivo che richiede la pagina e imposta i valori srcset e sizes nell'immagine in modo appropriato.

Il confronto seguente mostra come utilizzare la modalità layout per controllare le dimensioni dell'immagine su schermi diversi. Abbiamo utilizzato un'immagine demo condivisa nei documenti Next.js e visualizzata su uno smartphone e su un laptop standard.

Schermo di laptop Schermo dello smartphone
Layout = intrinseco: viene ridimensionato per adattarsi alla larghezza del contenitore nelle aree visibili più piccole. Non viene ridimensionato oltre le dimensioni intrinseche dell'immagine su un'area visibile più grande. La larghezza del container è al 100%
Immagine delle montagne mostrata così com&#39;è Immagine delle montagne ridimensionata
Layout = corretto: l'immagine non è reattiva. Larghezza e altezza sono fisse, in modo simile all'elemento "", indipendentemente dal dispositivo su cui viene eseguito il rendering.
Immagine delle montagne mostrata così com&#39;è L&#39;immagine delle montagne così com&#39;è non si adatta allo schermo
Layout = adattabile: fai lo scale down o lo scale up a seconda della larghezza del contenitore in aree visibili diverse, mantenendo le proporzioni.
Immagine delle montagne ingrandita per adattarsi allo schermo Immagine delle montagne ridimensionata per adattarsi allo schermo
Layout = riempimento: larghezza e altezza estese per riempire il contenitore principale. (Genitore "
" in questo esempio è impostata su 300*500)
Immagine delle montagne visualizzata per adattarsi al formato 300 x 500 Immagine delle montagne visualizzata per adattarsi al formato 300 x 500
Immagini visualizzate per layout diversi

Fornire il caricamento lento integrato

Il componente Immagine fornisce come impostazione predefinita una soluzione di caricamento lento integrata e ad alte prestazioni. Quando utilizzi l'elemento <img>, esistono alcune opzioni native per il caricamento lento, ma tutte hanno degli svantaggi che ne rendono difficile l'uso. Uno sviluppatore potrebbe adottare uno dei seguenti approcci al caricamento lento:

  • Specifica l'attributo loading: è facile da implementare, ma al momento è non supportato su alcuni browser.
  • Usa l'API Intersection Observationr: la creazione di una soluzione di caricamento lento personalizzata richiede impegno e design e implementazione ponderati. Gli sviluppatori potrebbero non avere sempre il tempo per farlo.
  • Importa una libreria di terze parti nelle immagini con caricamento lento. Potrebbe essere necessario un ulteriore sforzo per valutare e integrare una libreria di terze parti adatta per il caricamento lento.

Nel componente Immagine Next.js, il caricamento è impostato su "lazy" per impostazione predefinita. Il caricamento lento viene implementato utilizzando Intersection Observationr, disponibile nella maggior parte dei browser moderni. Gli sviluppatori non devono fare nulla per abilitarla, ma possono disattivarla quando necessario.

Precarica le immagini importanti

Molto spesso, gli elementi LCP sono immagini e le immagini di grandi dimensioni possono ritardare l'LCP. È consigliabile precaricare le immagini critiche in modo che il browser possa rilevarle prima. Quando utilizzi un elemento <img>, è possibile che venga incluso un suggerimento di precaricamento nell'intestazione HTML, come indicato di seguito.

<link rel="preload" as="image" href="important.png">

Un componente immagine ben progettato dovrebbe consentire di modificare la sequenza di caricamento delle immagini, indipendentemente dal framework utilizzato. Nel caso del componente Immagine Next.js, gli sviluppatori possono indicare un'immagine che è una buona candidata per il precaricamento utilizzando l'attributo priority del componente images.

<Image src="/hero.jpg" alt="hero" height="400" width="200" priority />

L'aggiunta di un attributo priority semplifica il markup ed è più comodo da utilizzare. Gli sviluppatori di componenti immagine possono anche esplorare le opzioni per applicare l'euristica in modo da automatizzare il precaricamento delle immagini above the fold sulla pagina che soddisfano criteri specifici.

Incoraggia l'hosting di immagini ad alte prestazioni

Le CDN di immagine sono consigliate per automatizzare l'ottimizzazione delle immagini e supportano anche formati di immagine moderni come WebP e AVIF. Il componente Immagine Next.js utilizza una CDN immagine per impostazione predefinita che impiega un'architettura di caricamento. L'esempio seguente mostra che il caricatore consente la configurazione della rete CDN nel file di configurazione Next.js.

module.exports = {
  images: {
    loader: 'imgix',
    path: 'https://ImgApp/imgix.net',
  },
}

Con questa configurazione, gli sviluppatori possono utilizzare URL relativi nell'origine dell'immagine e il framework concatena l'URL relativo con il percorso CDN per generare l'URL assoluto. Sono supportate le CDN immagine più diffuse come Imgix, Cloudinary e Akamai. L'architettura supporta l'utilizzo di un cloud provider personalizzato implementando una funzione loader personalizzata per l'app.

Supporta immagini ospitate autonomamente

Potrebbero verificarsi situazioni in cui i siti web non possono utilizzare CDN di immagini. In questi casi, un componente immagine deve supportare le immagini ospitate autonomamente. Il componente Immagine Next.js utilizza un ottimizzatore di immagini come server di immagini integrato che fornisce un'API simile a CDN. L'ottimizzatore utilizza Sharp per le trasformazioni dell'immagine di produzione, se è installato sul server. Questa libreria è ideale per chiunque voglia creare la propria pipeline di ottimizzazione delle immagini.

Supporta il caricamento progressivo

Il caricamento progressivo è una tecnica utilizzata per mantenere vivo l'interesse degli utenti mostrando un'immagine segnaposto di solito di qualità notevolmente inferiore durante il caricamento dell'immagine effettiva. Migliora il rendimento percepito e l'esperienza utente. Può essere utilizzata in combinazione con il caricamento lento per le immagini below the fold o above the fold.

Il componente Immagine Next.js supporta il caricamento progressivo dell'immagine tramite la proprietà placeholder. Può essere utilizzato come LQIP (segnaposto dell'immagine di bassa qualità) per mostrare un'immagine di bassa qualità o sfocata durante il caricamento dell'immagine.

Impatto

Con tutte le ottimizzazioni di cui sopra, abbiamo avuto successo con il componente Immagine Next.js in produzione e stiamo lavorando anche con altri stack tecnici su componenti immagine simili.

Quando Leboncoin ha eseguito la migrazione del frontend JavaScript precedente a Next.js, ha anche eseguito l'upgrade della pipeline delle immagini per utilizzare il componente Immagine Next.js. In una pagina sottoposta a migrazione da <img> a quella successiva/immagine, il valore LCP è sceso da 2,4 a 1,7 secondi. I byte totali di immagine scaricati per la pagina sono passati da 663 kB a 326 kB (con circa 100 kB di byte immagine caricati con il metodo lazy).

Lezioni apprese

Chiunque crei un'app Next.js può trarre vantaggio dall'utilizzo del componente Immagine Next.js per l'ottimizzazione. Tuttavia, se vuoi creare astrazioni di prestazioni simili per un altro framework o CMS, di seguito troverai alcune lezioni che abbiamo imparato durante questa procedura e che potrebbero essere utili.

Le valvole di sicurezza possono causare più danni che vantaggi

In una versione precedente del componente Immagine Next.js, abbiamo fornito un attributo unsized che consentiva agli sviluppatori di bypassare il requisito relativo alle dimensioni e di utilizzare immagini con dimensioni non specificate. Abbiamo pensato che sarebbe stato necessario nei casi in cui era impossibile conoscere in anticipo l'altezza o la larghezza dell'immagine. Tuttavia, abbiamo notato che gli utenti consigliano l'attributo unsized nei problemi di GitHub come soluzione generica a problemi relativi ai requisiti relativi alle dimensioni, anche nei casi in cui potrebbero risolvere il problema in modi che non hanno peggiorato la CLS. In seguito abbiamo ritirato e rimosso l'attributo unsized.

Separa le difficoltà utili da quelle inutili

Il requisito per il dimensionamento di un'immagine è un esempio di "attrito utile". Limita l'uso del componente, ma in cambio offre grandi vantaggi in termini di prestazioni. Gli utenti accetteranno prontamente il vincolo se hanno un quadro chiaro dei potenziali vantaggi in termini di prestazioni. Pertanto, vale la pena spiegare questo compromesso nella documentazione e in altro materiale pubblicato sul componente.

Tuttavia, è possibile trovare soluzioni alternative per questo problema senza sacrificare le prestazioni. Ad esempio, durante lo sviluppo del componente Immagine Next.js, abbiamo ricevuto reclami secondo cui era fastidioso cercare le dimensioni delle immagini memorizzate localmente. Abbiamo aggiunto importazioni di immagini statiche, che semplificano questo processo recuperando automaticamente le dimensioni delle immagini locali al momento della creazione utilizzando un plug-in Babel.

Trova un equilibrio tra funzionalità di praticità e ottimizzazioni del rendimento

Se il tuo componente di un'immagine non fa altro che imporre un "utile attrito" ai suoi utenti, gli sviluppatori tendono a non volerlo utilizzare. Abbiamo riscontrato che, sebbene le funzionalità relative alle prestazioni come il dimensionamento delle immagini e la generazione automatica di valori srcset, siano state le più importanti. Anche le funzionalità per gli sviluppatori, come il caricamento lento automatico e i segnaposto sfocati integrati, hanno suscitato interesse per il componente Immagine Next.js.

Imposta una roadmap per le funzionalità che ne promuovono l'adozione

Creare una soluzione che funzioni perfettamente per tutte le situazioni è molto difficile. Si può avere la tentazione di progettare qualcosa che funzioni bene per il 75% delle persone e poi dire all'altro 25% che "in questi casi questo componente non fa per te".

In pratica, questa strategia si rivela in contrasto con i tuoi obiettivi di progettista di componenti. Vuoi che gli sviluppatori adottino il tuo componente per sfruttarne i vantaggi in termini di rendimento. Questa operazione è difficile se c'è un contingente di utenti che non riesce a eseguire la migrazione e si sente escluso dalla conversazione. È probabile che esprimano delusione, generando percezioni negative che influiscono sull'adozione.

È consigliabile avere una roadmap per il componente che copra tutti i casi d'uso ragionevoli sul lungo periodo. Inoltre, è utile indicare in modo esplicito nella documentazione che cosa non è supportato e perché, al fine di definire le aspettative in merito ai problemi che il componente è destinato a risolvere.

Conclusione

L'utilizzo e l'ottimizzazione delle immagini sono complicate. Gli sviluppatori devono trovare l'equilibrio tra prestazioni e qualità delle immagini, garantendo al contempo un'esperienza utente ottimale. Per questo l'ottimizzazione delle immagini è un'impresa ad alto costo e di grande impatto.

Anziché lasciare che ogni app reinventasse sempre il volante, abbiamo creato un modello di best practice che sviluppatori, framework e altri stack tecnici potevano usare come riferimento per le proprie implementazioni. Questa esperienza si rivelerà davvero utile poiché supportiamo altri framework sui relativi componenti di immagine.

Il componente Image Next.js ha migliorato i risultati delle prestazioni nelle applicazioni Next.js, migliorando così l'esperienza utente. Crediamo che sia un ottimo modello che funzionerebbe bene nell'ecosistema più ampio e ci piacerebbe ricevere il parere degli sviluppatori che vorrebbero adottare questo modello nei loro progetti.