Sintassi descrittive

In questo modulo imparerai a offrire al browser una scelta di immagini in modo da poter prendere le migliori decisioni su cosa visualizzare. srcset non è un metodo per scambiare le origini delle immagini in punti di interruzione specifici e non ha lo scopo di scambiare un'immagine con un'altra. Queste sintassi consentono al browser di risolvere un problema molto difficile indipendentemente da noi: richiedere e visualizzare in modo fluido un'origine immagine in base al contesto di navigazione dell'utente, inclusi le dimensioni dell'area visibile, la densità della visualizzazione, le preferenze dell'utente, la larghezza di banda e innumerevoli altri fattori.

È una domanda importante, decisamente più di quanto non dovremmo prendere in considerazione quando stiamo semplicemente eseguendo il markup di un'immagine per il web e farlo bene comporta più informazioni di quante possiamo accedere.

Descrizione della densità con x

Un elemento <img> con larghezza fissa occuperà la stessa quantità dell'area visibile in qualsiasi contesto di navigazione, indipendentemente dalla densità del display di un utente, ovvero dal numero di pixel fisici che compongono lo schermo. Ad esempio, un'immagine con una larghezza intrinseca di 400px occuperà quasi l'intera area visibile del browser sia sul Google Pixel originale che su Pixel 6 Pro molto più recente: entrambi i dispositivi hanno un'area visibile ampia di 412px pixel logico normalizzata.

Pixel 6 Pro ha un display molto più nitido, tuttavia: il 6 Pro ha una risoluzione fisica di 1440 × 3120 pixel, mentre il Pixel è di 1080 × 1920 pixel, ovvero il numero di pixel hardware che compongono lo schermo stesso.

Il rapporto tra i pixel logici e i pixel fisici di un dispositivo corrisponde al rapporto pixel del dispositivo per il display (DPR). Il DPR viene calcolato dividendo la risoluzione effettiva dello schermo del dispositivo per i pixel CSS dell'area visibile.

Un DPR pari a 2 visualizzato in una finestra della console.

Quindi, il Pixel originale ha un DPR di 2,6, mentre il Pixel 6 Pro ha una DPR di 3,5.

L'iPhone 4, il primo dispositivo con un DPR maggiore di 1, riporta un rapporto pixel del dispositivo pari a 2: la risoluzione fisica dello schermo è il doppio della risoluzione logica. Qualsiasi dispositivo prima dell'iPhone 4 aveva un DPR di 1: un pixel logico per un pixel fisico.

Se visualizzi quell'immagine a livello di 400px su un display con DPR pari a 2, ogni pixel logico viene visualizzato su quattro pixel fisici del display: due orizzontali e due verticali. L'immagine non beneficia del display ad alta densità, in quanto avrà lo stesso aspetto di un display con DPR pari a 1. Ovviamente, tutto ciò che viene "disegnato" dal motore di rendering del browser, ad esempio testo, forme CSS o SVG, verrà adattato alla visualizzazione a densità più elevata. Tuttavia, come hai appreso da Formati e compressione delle immagini, le immagini raster sono griglie fisse di pixel. Anche se non sempre appare chiaramente evidente, un'immagine raster ridimensionata per essere adattata a un display a densità più elevata avrà una risoluzione bassa rispetto alla pagina circostante.

Per evitare questo ridimensionamento, l'immagine visualizzata deve avere una larghezza intrinseca di almeno 800 pixel. Se ridimensionata per adattarsi a uno spazio in un layout con una larghezza di 400 pixel logici, l'origine dell'immagine da 800 pixel ha il doppio della densità in pixel e sarà bello e nitido su un display con DPR pari a 2.

Primo piano di un petalo di fiore che mostra disparità di densità.

Poiché un display con DPR pari a 1 non può utilizzare la maggiore densità di un'immagine, verrà ridimensionato in base al display e, come sai, un'immagine ridimensionata verrà visualizzata correttamente. Su un display a bassa densità, un'immagine adatta a schermi ad alta densità sarà quella di qualsiasi altra immagine a bassa densità.

Come hai appreso nella sezione Immagini e prestazioni, un utente con un display a bassa densità che visualizza un'origine immagine con lo scale down a 400px bisogno solo di un'origine con una larghezza intrinseca di 400px. Un'immagine molto più grande potrebbe essere adatta a tutti gli utenti, ma un'immagine enorme e ad alta risoluzione visualizzata su un display piccolo e a bassa densità sarà come qualsiasi altra immagine piccola e a bassa densità, ma sarà molto più lenta.

Come puoi immaginare, i dispositivi mobili con un DPR pari a 1 sono terribilmente rari, anche se sono ancora comuni nei contesti di navigazione "desktop". Secondo i dati condivisi da Matt Hobbs, circa il 18% delle sessioni di navigazione del GOV.UK da novembre 2022 segnala una DPR pari a 1. Anche se le immagini ad alta densità potrebbero avere un aspetto simile a quello che gli utenti potrebbero aspettarsi, avranno una larghezza di banda e costi di elaborazione molto più elevati, soprattutto per gli utenti dei dispositivi meno recenti e meno potenti che probabilmente dispongono ancora di display a bassa densità.

L'uso di srcset garantisce che solo i dispositivi con display ad alta risoluzione ricevano origini delle immagini abbastanza grandi da risultare nitide, senza trasferire lo stesso costo della larghezza di banda agli utenti con display a risoluzione più bassa.

L'attributo srcset identifica uno o più candidati separati da virgole per il rendering di un'immagine. Ogni candidato è costituito da due elementi: un URL, come useresti in src e una sintassi che descrive l'origine dell'immagine. Ogni candidato in srcset è descritto dalla sua larghezza intrinseca ("sintassi w") o dalla densità prevista ("sintassi x").

La sintassi x è un'abbreviazione di "questa fonte è appropriata per una visualizzazione con questa densità". Un candidato seguito da 2x è appropriato per una visualizzazione con un DPR pari a 2.

<img src="low-density.jpg" srcset="double-density.jpg 2x" alt="...">

Ai browser che supportano srcset verranno presentati due candidati: double-density.jpg, che 2x descrive come appropriato per gli schermi con un DPR pari a 2 e low-density.jpg nell'attributo src: il candidato selezionato se non è stato trovato nulla di più appropriato in srcset. Per i browser che non supportano srcset, l'attributo e i suoi contenuti verranno ignorati e i contenuti di src verranno richiesti, come di consueto.

È facile confondere i valori specificati nell'attributo srcset per ottenere istruzioni. Questo 2x comunica al browser che il file di origine associato sarebbe adatto per l'utilizzo su uno schermo con DPR pari a 2, ovvero informazioni sulla fonte stessa. Non indica al browser come utilizzare l'origine, ma informa solo il browser come potrebbe essere utilizzata l'origine. Si tratta di una distinzione sottile ma importante: si tratta di un'immagine a doppia densità, non di un'immagine da utilizzare su un display a doppia densità.

La differenza tra la sintassi "questa fonte è appropriata per i display 2x" e quella con la dicitura "utilizza questa fonte sui display 2x" è leggera nella stampa, ma la densità di visualizzazione è solo uno dei numerosi fattori collegati che il browser utilizza per decidere il candidato da visualizzare, di cui solo alcuni puoi conoscere. Ad esempio, singolarmente puoi determinare che un utente ha attivato una preferenza del browser per risparmiare larghezza di banda tramite la query multimediale prefers-reduced-data e utilizzarla per attivare sempre gli utenti per le immagini a bassa densità indipendentemente dalla densità del display. Tuttavia, a meno che non vengano implementate in modo coerente, da ogni sviluppatore, su ogni sito web, non sarebbe di grande utilità per un utente. In un sito potrebbero rispettare le loro preferenze, mentre su un sito successivo si imbattono in una parete di immagini che cancella la larghezza di banda.

L'algoritmo di selezione delle risorse volutamente vago utilizzato da srcset/sizes lascia ai browser l'opportunità di decidere di selezionare immagini a bassa densità con cali della larghezza di banda o in base a una preferenza per ridurre al minimo l'utilizzo dei dati, senza che Google si assuma la responsabilità di come, quando o a quale soglia. Non ha senso assumersi responsabilità (e lavoro aggiuntivo) che il browser è in grado di gestire meglio per te.

Descrizione delle larghezze con w

srcset accetta un secondo tipo di descrittore per le origini delle immagini candidati. È molto più potente e, per i nostri scopi, molto più facile da capire. Anziché segnalare un candidato come dotato delle dimensioni appropriate per una determinata densità di visualizzazione, la sintassi w descrive la larghezza intrinseca di ogni origine candidata. Anche in questo caso, ogni candidato è identico, salva gli stessi contenuti, lo stesso ritaglio e le stesse proporzioni. Ma in questo caso, vuoi che il browser dell'utente scelga tra due candidati: small.jpg, una sorgente con una larghezza intrinseca di 600 px e large.jpg, un'origine con una larghezza intrinseca di 1200 px.

srcset="small.jpg 600w, large.jpg 1200w"

Questa informazione non indica al browser cosa fare, ma fornisce solo un elenco di candidati per la visualizzazione dell'immagine. Prima che il browser possa decidere quale origine eseguire il rendering, devi fornire qualche informazione in più: una descrizione di come l'immagine verrà visualizzata nella pagina. Per farlo, utilizza l'attributo sizes.

Descrizione dell'utilizzo con sizes

I browser sono incredibilmente performanti per il trasferimento delle immagini. Le richieste di asset immagine vengono avviate molto prima delle richieste per i fogli di stile o il codice JavaScript, spesso anche prima che il markup sia stato analizzato completamente. Quando il browser effettua queste richieste, non ha informazioni sulla pagina stessa, a parte il markup, e potrebbe non aver ancora avviato richieste per fogli di stile esterni, figurarsi l'applicazione. Quando il browser analizza il tuo markup e inizia a effettuare richieste esterne, contiene solo informazioni a livello di browser: dimensioni dell'area visibile dell'utente, densità in pixel del display dell'utente, preferenze dell'utente e così via.

Questo dato non ci fornisce informazioni su come deve essere visualizzato un'immagine nel layout della pagina: non può neanche utilizzare l'area visibile come proxy per il limite superiore della dimensione img, in quanto potrebbe occupare un contenitore a scorrimento orizzontale. Dobbiamo quindi fornire al browser queste informazioni e farlo utilizzando il markup. Questo è tutto ciò che possiamo utilizzare per queste richieste.

Come srcset, sizes ha lo scopo di rendere disponibili le informazioni su un'immagine non appena viene analizzato il markup. Proprio come l'attributo srcset è un'abbreviazione per "ecco i file sorgente e le relative dimensioni intrinseche", l'attributo sizes è l'abbreviazione di "qui è la dimensione dell'immagine visualizzata nel layout". Il modo in cui descrivi l'immagine è relativo all'area visibile. Anche in questo caso, le dimensioni dell'area visibile sono l'unica informazione sul layout di cui il browser dispone quando viene effettuata la richiesta dell'immagine.

Potrebbe sembrare un po' contorta nella stampa, ma nella pratica è molto più facile da capire:

<img
 sizes="80vw"
 srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
 src="fallback.jpg"
 alt="...">

In questo caso, questo valore sizes indica al browser che lo spazio occupato da img nel nostro layout ha una larghezza compresa tra 80vw e l'80% dell'area visibile. Ricorda che questa non è un'istruzione, ma una descrizione delle dimensioni dell'immagine nel layout della pagina. Non dice "Fai in modo che questa immagine occupi l'80% dell'area visibile", ma "questa immagine occuperà l'80% dell'area visibile dopo il rendering della pagina".

In qualità di sviluppatore, hai finito. Hai descritto con precisione un elenco di origini candidature in srcset e la larghezza dell'immagine in sizes e, proprio come per la sintassi x in srcset, il resto spetta al browser.

Tuttavia, per comprendere appieno come vengono utilizzate queste informazioni, prendiamoci un momento per esaminare le decisioni che il browser di un utente prende quando si trova questo markup:

Hai comunicato al browser che questa immagine occuperà l'80% dell'area visibile disponibile; pertanto, se dovessimo eseguire il rendering di questo img su un dispositivo con un'area visibile da 1000 pixel, l'immagine occuperà 800 pixel. Il browser prenderà quindi questo valore e dividerà le larghezze di ciascuna delle origini immagine candidati specificate in srcset. La sorgente più piccola ha una dimensione intrinseca di 600 pixel, quindi: 600÷800=0,75. La nostra immagine media è larga 1200 pixel: 1200÷800=1,5. La nostra immagine più grande è larga 2000 pixel: 2000÷800=2,5.

I risultati di questi calcoli (.75, 1.5 e 2.5) sono, di fatto, opzioni DPR personalizzate specificamente in base alle dimensioni dell'area visibile dell'utente. Poiché il browser dispone anche di informazioni sulla densità dello schermo dell'utente, prende una serie di decisioni:

Con queste dimensioni dell'area visibile, il candidato small.jpg viene ignorato indipendentemente dalla densità dell'annuncio: con un DPR calcolato inferiore a 1, questa origine richiederebbe un upscaling per qualsiasi utente, quindi non è appropriato. Su un dispositivo con un DPR pari a 1, medium.jpg fornisce la corrispondenza più simile, ovvero la sorgente è appropriata per la visualizzazione con un DPR pari a 1.5, pertanto è leggermente più grande del necessario, ma ricorda che il downscaling è un processo visivamente semplice. Su un dispositivo con DPR pari a 2,large.jpg è la corrispondenza più vicina, quindi viene selezionato.

Se la stessa immagine viene visualizzata in un'area visibile larga 600 pixel, il risultato di tutti questi calcoli matematici sarebbe completamente diverso: 80vw ora corrisponde a 480 px. Se dividiamo le larghezze delle origini per questo valore, otteniamo 1.25, 2.5 e 4.1666666667. Con queste dimensioni dell'area visibile, small.jpg verrà scelto su dispositivi 1x e medium.jpg corrisponderà a dispositivi 2x.

L'immagine sarà identica in tutti questi contesti di navigazione: tutti i nostri file di origine sono esattamente uguali, tranne che per le dimensioni, e ogni file viene visualizzato con la stessa luminosità consentita dalla densità della visualizzazione dell'utente. Tuttavia, invece di mostrare large.jpg per ogni utente per supportare le aree visibili più grandi e i display con la massima densità, agli utenti verrà sempre mostrato il candidato più piccolo. Se utilizzi una sintassi descrittiva invece di una prescrittiva, non devi impostare manualmente i punti di interruzione e prendere in considerazione le aree visibili e i DPR futuri: dovrai semplicemente fornire al browser delle informazioni e consentirgli di determinare le risposte.

Poiché il nostro valore sizes è relativo all'area visibile e completamente indipendente dal layout della pagina, aggiunge un livello di complicazione. È raro avere un'immagine che occupa solo una percentuale dell'area visibile, senza margini a larghezza fissa, spaziatura interna o influenza da parte di altri elementi sulla pagina. Spesso è necessario esprimere la larghezza di un'immagine utilizzando una combinazione di unità, percentuali, em, px e così via.

Fortunatamente, puoi utilizzare calc() qui. Qualsiasi browser con supporto nativo per le immagini adattabili supporterà anche calc(), consentendoci di combinare le unità CSS, ad esempio un'immagine che occupa l'intera larghezza dell'area visibile dell'utente, meno un margine di 1em su entrambi i lati:

<img
    sizes="calc(100vw-2em)"
    srcset="small.jpg 400w, medium.jpg 800w, large.jpg 1600w, x-large.jpg 2400w"
    src="fallback.jpg"
    alt="...">

Descrizione dei punti di interruzione

Se hai dedicato molto tempo a lavorare con i layout adattabili, probabilmente avrai notato qualcosa che manca in questi esempi: lo spazio occupato da un'immagine in un layout molto probabilmente cambi tra i punti di interruzione del layout. In questo caso, devi trasmettere al browser alcuni dettagli in più: sizes accetta un insieme di candidati separati da virgole per le dimensioni visualizzate dell'immagine, così come srcset accetta candidati separati da virgole per le origini delle immagini. Queste condizioni utilizzano la familiare sintassi delle query multimediali. Questa sintassi è la prima corrispondenza: non appena viene soddisfatta una condizione del supporto, il browser interrompe l'analisi dell'attributo sizes e viene applicato il valore specificato.

Supponiamo che tu abbia un'immagine destinata a occupare l'80% dell'area visibile, meno un em di spaziatura interna su entrambi i lati, nelle aree visibili superiori a 1200 px. Nelle aree visibili più piccole, occupa l'intera larghezza dell'area visibile.

  <img
     sizes="(min-width: 1200px) calc(80vw - 2em), 100vw"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

Se l'area visibile dell'utente è superiore a 1200 px, calc(80vw - 2em) descrive la larghezza dell'immagine nel nostro layout. Se la condizione (min-width: 1200px) non corrisponde, il browser passa al valore successivo. Poiché non esiste una condizione dei contenuti multimediali specifica associata a questo valore, viene utilizzato 100vw come impostazione predefinita. Se dovessi scrivere questo attributo sizes utilizzando max-width query supporti:

  <img
     sizes="(max-width: 1200px) 100vw, calc(80vw - 2em)"
     srcset="small.jpg 600w, medium.jpg 1200w, large.jpg 2000w"
     src="fallback.jpg"
     alt="...">

Usa un linguaggio semplice: "(max-width: 1200px) corrisponde? In caso contrario, vai avanti. Il valore successivo, calc(80vw - 2em), non ha condizione idonea, quindi è quello selezionato.

Ora che hai fornito al browser tutte queste informazioni sul tuo elemento img (sorgenti potenziali, larghezze intrinseche e come intendi presentare l'immagine all'utente), il browser utilizza un insieme parziale di regole per determinare l'utilizzo di tali informazioni. Se sembra vago, beh, è perché lo è, in base alla progettazione. L'algoritmo di selezione della sorgente codificato nella specifica HTML è esplicitamente vago su come dovrebbe essere scelta una fonte. Una volta analizzati le origini, i relativi descrittori e la modalità di rendering dell'immagine, il browser è libero di fare ciò che vuole: non puoi sapere con certezza quale fonte verrà scelta dal browser.

Una sintassi che dice "Utilizza questa sorgente su uno schermo ad alta risoluzione" è prevedibile, ma non risolve il problema principale delle immagini in un layout adattabile, ovvero la conservazione della larghezza di banda dell'utente. La densità dei pixel di uno schermo è correlata solo tangenzialmente alla velocità della connessione a internet. Se usi un laptop top di gamma, ma navighi sul web tramite una connessione a consumo, in tethering con il telefono o se usi una connessione Wi-Fi in aereo instabile, ti consigliamo di disattivare le origini delle immagini ad alta risoluzione, indipendentemente dalla qualità del display.

Lasciare l'ultima parola al browser consente di migliorare le prestazioni molto più di quanto potremmo gestire con una sintassi rigorosamente prescrittiva. Ad esempio, nella maggior parte dei browser, un img che utilizza la sintassi srcset o sizes non si preoccuperà mai di richiedere un'origine con dimensioni più piccole di una che l'utente ha già nella cache del browser. Perché sarebbe utile fare una nuova richiesta per un'origine che sarebbe identica, quando il browser può scalare facilmente l'origine dell'immagine che ha già? Tuttavia, se l'utente scala l'area visibile fino al punto in cui è necessaria una nuova immagine per evitare il ridimensionamento, quella richiesta verrà comunque effettuata, quindi tutto avrà l'aspetto previsto.

Questa mancanza di controllo esplicito può sembrare un po' inquietante, ma poiché stai utilizzando file sorgente con contenuti identici, è molto più probabile che gli utenti abbiano un'esperienza "interrotta" rispetto a un'unica fonte src, indipendentemente dalle decisioni prese dal browser.

Utilizzo di sizes e srcset

Si tratta di molte informazioni, sia per te, per il lettore, sia per il browser. srcset e sizes sono due sintassi dense che descrivono una quantità scioccante di informazioni in relativamente pochi caratteri. Il che significa, nel bene e nel male, in base alla progettazione: rendere queste sintassi meno generiche e analizzate più facilmente da parte degli utenti, avrebbe potuto renderle più difficili da analizzare per un browser. Maggiore è la complessità aggiunta a una stringa, maggiore è la possibilità che si verifichino errori del parser o differenze non intenzionali di comportamento da un browser all'altro. C'è comunque un vantaggio in questo caso: una sintassi più facile da leggere dalle macchine è una sintassi più facile da scrivere.

srcset è un caso chiaro per l'automazione. Raramente creerai manualmente più versioni delle tue immagini per un ambiente di produzione, invece di automatizzare il processo utilizzando un'esecuzione delle attività come Gulp, un bundler come Webpack, un CDN di terze parti come Cloudinary o una funzionalità già integrata nel tuo CMS preferito. Innanzitutto, se le informazioni fornite sono sufficienti per generare le nostre fonti, un sistema avrebbe abbastanza informazioni per scriverle in un attributo srcset valido.

sizes è un po' più difficile da automatizzare. Come sai, l'unico modo in cui un sistema può calcolare le dimensioni di un'immagine in un layout sottoposto a rendering è quello di eseguire il rendering del layout. Fortunatamente, sono emersi diversi strumenti per sviluppatori per astrare il processo di scrittura a mano degli attributi sizes, con un'efficienza che non potresti mai trovare manualmente. respImageLint, ad esempio, è uno snippet di codice destinato a controllare l'accuratezza degli attributi sizes e a fornire suggerimenti per migliorarli. Il progetto Lazysizes compromette la velocità dell'efficienza posticipando le richieste di immagine fino a quando il layout non è stato stabilito, consentendo a JavaScript di generare automaticamente valori sizes. Se utilizzi un framework di rendering completamente lato client come React o Vue, esistono diverse soluzioni per la creazione e/o la generazione degli attributi srcset e sizes, di cui parleremo più dettagliatamente in CMS e framework.