Una delle caratteristiche del panorama attuale dei dispositivi è la disponibilità di una gamma molto ampia di densità di pixel dello schermo. Alcuni dispositivi sono dotati di display con risoluzione molto elevata, mentre altri rimangono indietro. Gli sviluppatori di applicazioni devono supportare una serie di densità di pixel, il che può essere piuttosto impegnativo. Sul web mobile, le difficoltà sono aggravate da diversi fattori:
- Ampia gamma di dispositivi con diversi fattori di forma.
- Larghezza di banda della rete e durata della batteria limitate.
In termini di immagini, l'obiettivo degli sviluppatori di app web è pubblicare le immagini di migliore qualità nel modo più efficiente possibile. Questo articolo illustra alcune tecniche utili per farlo oggi e nel prossimo futuro.
Se possibile, evita le immagini
Prima di aprire questa scatola di vermi, ricorda che il web offre molte tecnologie potenti che sono in gran parte indipendenti da risoluzione e DPI. Nello specifico, testo, SVG e gran parte del CSS "funzionano e basta" grazie alla funzionalità di ridimensionamento automatico dei pixel del web (tramite devicePixelRatio).
Detto questo, non è sempre possibile evitare le immagini raster. Ad esempio, potresti avere asset che sarebbero piuttosto difficili da replicare in SVG/CSS puro o avere a che fare con una fotografia. Sebbene sia possibile convertire automaticamente l'immagine in SVG, la vettorizzazione delle fotografie ha poco senso perché le versioni ingrandite di solito non hanno un bell'aspetto.
Sfondo
Una breve storia della densità di visualizzazione
All'inizio, i display dei computer avevano una densità di pixel di 72 o 96 dpi (dots per inch).
La densità di pixel dei display è migliorata gradualmente, in gran parte grazie al caso d'uso mobile, in cui gli utenti in genere tengono gli smartphone più vicini al viso, rendendo i pixel più visibili. Entro il 2008, gli smartphone da 150 dpi erano la nuova norma. La tendenza all'aumento della densità del display è continuata e oggi i nuovi smartphone montano display da 300 dpi (marcati "Retina" da Apple).
L'obiettivo finale, ovviamente, è un display in cui i pixel sono completamente invisibili. Per il fattore di forma dello smartphone, l'attuale generazione di display Retina/HiDPI potrebbe essere vicina a questo ideale. Tuttavia, è probabile che nuove categorie di hardware e dispositivi indossabili come Project Glass continueranno a incrementare la densità di pixel.
In pratica, le immagini a bassa densità dovrebbero avere lo stesso aspetto sulle nuove schermate come su quelle vecchie, ma rispetto alle immagini nitide ad alta densità che gli utenti sono abituati a vedere, le immagini a bassa densità sembrano sgradevoli e pixelated. Di seguito è riportata una simulazione approssimativa dell'aspetto di un'immagine 1x su un display 2x. Al contrario, l'immagine 2x è di buona qualità.


Pixel sul web
Quando il web è stato progettato, il 99% dei display aveva una risoluzione di 96 dpi (o fingeva di esserlo) e sono state prese poche disposizioni per le variazioni in questo campo. A causa di una grande variazione nelle dimensioni e nelle densità degli schermi, era necessario un metodo standard per migliorare la visualizzazione delle immagini su una serie di dimensioni e densità dello schermo.
La specifica HTML ha recentemente affrontato questo problema definendo un pixel di riferimento utilizzato dai produttori per determinare le dimensioni di un pixel CSS.
Utilizzando il pixel di riferimento, un produttore può determinare le dimensioni del pixel fisico del dispositivo rispetto al pixel standard o ideale. Questo rapporto è chiamato proporzioni pixel del dispositivo.
Calcolo del rapporto pixel del dispositivo
Supponiamo che uno smartphone abbia uno schermo con una dimensione fisica dei pixel di 180 pixel per pollice (ppi). Il calcolo del rapporto pixel del dispositivo richiede tre passaggi:
Confronta la distanza effettiva a cui viene tenuto il dispositivo con la distanza per il pixel di riferimento.
In base alle specifiche, sappiamo che a 71 cm l'ideale è 96 pixel per pollice. Tuttavia, poiché si tratta di uno smartphone, le persone tengono il dispositivo più vicino al viso rispetto a un laptop. Stimiamo che la distanza sia di 45 centimetri.
Moltiplica il rapporto di distanza per la densità standard (96 ppi) per ottenere la densità di pixel ideale per la distanza indicata.
idealPixelDensity = (28/18) * 96 = 150 pixel per pollice (circa)
Prendi il rapporto tra la densità dei pixel fisica e la densità dei pixel ideale per ottenere il rapporto pixel del dispositivo.
devicePixelRatio
= 180/150 = 1,2

Ora, quando un browser deve sapere come ridimensionare un'immagine in modo che si adatti allo schermo in base alla risoluzione ideale o standard, fa riferimento alle proporzioni pixel del dispositivo di 1,2, il che significa che per ogni pixel ideale questo dispositivo ha 1,2 pixel fisici. La formula per passare dai pixel ideali (come definiti dalle specifiche web) a quelli fisici (punti sullo schermo del dispositivo) è la seguente:
physicalPixels = window.devicePixelRatio * idealPixels
In passato, i fornitori di dispositivi tendevano ad arrotondare i devicePixelRatios
(DPR). Gli iPhone e gli iPad di Apple riportano un DPR pari a 1, mentre i loro equivalenti Retina riportano 2. La specifica CSS consiglia di
l'unità di misura dei pixel si riferisce al numero intero di pixel del dispositivo che approssima al meglio il pixel di riferimento.
Uno dei motivi per cui i rapporti arrotondati possono essere migliori è che possono portare a meno artefatti subpixel.
Tuttavia, la realtà del panorama dei dispositivi è molto più varia e spesso gli smartphone Android hanno un DPR di 1, 5. Il tablet Nexus 7 ha un DPR di circa 1,33, ottenuto con un calcolo simile a quello riportato sopra. In futuro, prevediamo di aggiungere altri dispositivi con DPR variabili. Per questo motivo, non dovresti mai presumere che i tuoi clienti avranno DPR interi.
Panoramica delle tecniche di immagini HiDPI
Esistono molte tecniche per risolvere il problema di mostrare le immagini di miglior qualità il più rapidamente possibile, che rientrano in due categorie generali:
- Ottimizzazione di singole immagini e
- Ottimizzazione della selezione tra più immagini.
Approcci con una singola immagine: utilizza un'immagine, ma fai qualcosa di intelligente. Questi approcci hanno lo svantaggio di sacrificare inevitabilmente le prestazioni, poiché scaricherai immagini HiDPI anche su dispositivi meno recenti con DPI inferiori. Ecco alcuni approcci per il caso di singola immagine:
- Immagine HiDPI molto compressa
- Formato dell'immagine assolutamente fantastico
- Formato dell'immagine progressiva
Approcci con più immagini: utilizza più immagini, ma fai qualcosa di intelligente per scegliere quali caricare. Questi approcci comportano un overhead intrinseco per lo sviluppatore, che deve creare più versioni dello stesso asset e poi elaborare una strategia decisionale. Le opzioni sono:
- JavaScript
- Pubblicazione lato server
- Query sui media CSS
- Funzionalità del browser integrate (
image-set()
,<img srcset>
)
Immagine HiDPI molto compressa
Le immagini rappresentano già il 60% della larghezza di banda utilizzata per scaricare un sito web medio. Se pubblicheremo immagini HiDPI per tutti i clienti, questo numero aumenterà. Quanto aumenterà di dimensioni?
Ho eseguito alcuni test che hanno generato frammenti di immagini 1x e 2x con qualità JPEG a 90, 50 e 20. Ecco lo script shell che ho utilizzato (con ImageMagick) per generarli:



Da questo piccolo e non scientifico campionamento, sembra che la compressione delle immagini di grandi dimensioni offra un buon compromesso tra qualità e dimensioni. A mio avviso, le immagini con una compressione elevata sono migliori delle immagini non compresse.
Ovviamente, pubblicare immagini 2x di bassa qualità e altamente compresse su dispositivi 2x è peggiore che pubblicarne di qualità superiore e l'approccio sopra indicato comporta penalizzazioni per la qualità delle immagini. Se confronti la qualità: 90 immagini con la qualità: 20 immagini, noterai un calo della nitidezza e un aumento della granulosità. Questi artefatti potrebbero non essere accettabili nei casi in cui le immagini di alta qualità sono fondamentali (ad esempio, un'applicazione di visualizzazione di foto) o per gli sviluppatori di app che non vogliono scendere a compromessi.
Il confronto riportato sopra è stato effettuato interamente con file JPEG compressi. Vale la pena ricordare che esistono molti compromessi tra i formati delle immagini ampiamente implementati (JPEG, PNG, GIF), il che ci porta a…
Formato dell'immagine assolutamente fantastico
WebP è un formato di immagine molto accattivante che comprime molto bene le immagini mantenendone un'elevata fedeltà. Naturalmente, non è ancora implementato su tutti i dispositivi.
Un modo per verificare il supporto di WebP è tramite JavaScript. Carichi un'immagine di 1 px tramite data-uri, attendi l'attivazione di eventi di caricamento o errore e poi verifica che le dimensioni siano corrette. Modernizr è fornito con un script di rilevamento delle funzionalità, disponibile tramite Modernizr.webp
.
Tuttavia, un modo migliore per farlo è direttamente nel CSS utilizzando la funzione image(). Pertanto, se hai un'immagine WebP e un'immagine JPEG di riserva, puoi scrivere quanto segue:
#pic {
background: image("foo.webp", "foo.jpg");
}
Questo approccio presenta alcuni problemi. Innanzitutto, image()
non è affatto implementato su larga scala. In secondo luogo, anche se la compressione WebP supera di gran lunga quella JPEG, si tratta comunque di un miglioramento relativamente incrementale: le dimensioni sono ridotte di circa il 30% in base a questa galleria WebP. Pertanto, WebP
da solo non è sufficiente per risolvere il problema dell'elevato DPI.
Formati di immagini progressive
I formati delle immagini progressive come JPEG 2000, JPEG progressivo, PNG progressivo e GIF hanno il vantaggio (alquanto dibattuto) di mostrare l'immagine prima che venga caricata completamente. Potrebbero comportare un sovraccarico delle dimensioni, anche se esistono prove contrastanti in merito. Jeff Atwood ha affermato che la modalità progressiva "aumenta di circa il 20% le dimensioni delle immagini PNG e di circa il 10% le dimensioni delle immagini JPEG e GIF". Tuttavia, Stoyan Stefanov ha affermato che per i file di grandi dimensioni, la modalità progressiva è più efficiente (nella maggior parte dei casi).
A prima vista, le immagini progressive sembrano molto promettenti nel contesto della pubblicazione di immagini di qualità ottimale il più rapidamente possibile. L'idea è che il browser possa interrompere il download e la decodifica di un'immagine quando sa che i dati aggiuntivi non miglioreranno la qualità dell'immagine (ovvero tutti i miglioramenti della fedeltà sono a livello di subpixel).
Sebbene le connessioni siano facili da terminare, spesso è costoso riavviarle. Per un sito con molte immagini, l'approccio più efficiente è mantenere attiva una singola connessione HTTP, riutilizzandola il più a lungo possibile. Se la connessione viene interrotta prematuramente perché un'immagine è stata scaricata in misura sufficiente, il browser deve creare una nuova connessione, che può essere molto lenta in ambienti a bassa latenza.
Una soluzione alternativa è utilizzare la richiesta Intervallo HTTP, che consente ai browser di specificare un intervallo di byte da recuperare. Un browser intelligente potrebbe effettuare una richiesta HEAD per recuperare l'intestazione, elaborarla, decidere quanta parte dell'immagine è effettivamente necessaria ed eseguire il recupero. Purtroppo, l'intervallo HTTP è supportato male nei server web, il che rende poco pratico questo approccio.
Infine, un'ovvia limitazione di questo approccio è che non puoi scegliere quale immagine caricare, ma solo variare la fedeltà della stessa immagine. Di conseguenza, non viene affrontato il caso d'uso "Art direction".
Utilizzare JavaScript per decidere quale immagine caricare
Il primo e più ovvio approccio per decidere quale immagine caricare è
utilizzare JavaScript nel client. Questo approccio ti consente di scoprire tutto sul tuo user agent e di adottare il comportamento corretto. Puoi determinare il rapporto pixel del dispositivo tramite window.devicePixelRatio
, ottenere la larghezza e l'altezza dello schermo e persino eseguire lo sniffing della connessione di rete tramite navigator.connection o inviando una richiesta falsa, come fa la libreria foresight.js. Dopo aver raccolto tutte queste informazioni, puoi decidere quale immagine caricare.
Esistono circa un milione di librerie JavaScript che fanno qualcosa di simile a quanto indicato sopra e, purtroppo, nessuna di queste è particolarmente eccezionale.
Uno svantaggio importante di questo approccio è che l'utilizzo di JavaScript comporta un ritardo nel caricamento delle immagini fino al termine dell'analisi anticipata. Ciò significa essenzialmente che il download delle immagini non inizierà nemmeno dopo l'attivazione dell'evento pageload
. Scopri di più in questo
articolo di Jason Grigsby.
Decidere quale immagine caricare sul server
Puoi rimandare la decisione al lato server scrivendo gestori delle richieste personalizzati per ogni immagine pubblicata. Un gestore di questo tipo controllerebbe il supporto di Retina in base all'User-Agent (l'unica informazione inoltrata al server). Poi, a seconda che la logica lato server voglia pubblicare asset HiDPI, carica l'asset appropriato (nominato in base a una convenzione nota).
Purtroppo, l'User-Agent non fornisce necessariamente informazioni sufficienti per decidere se un dispositivo deve ricevere immagini di alta o bassa qualità. Inoltre, è ovvio che tutto ciò che riguarda l'User-Agent è un hack e deve essere evitato, se possibile.
Utilizzare le query sui media CSS
Poiché sono dichiarative, le query sui media CSS ti consentono di dichiarare la tua intenzione e lasciare che il browser agisca per tuo conto. Oltre all'uso più comune delle query supporti, ovvero la corrispondenza alle dimensioni del dispositivo, puoi anche eseguire la corrispondenza a devicePixelRatio
. La query sui media associata è
device-pixel-ratio e ha varianti minime e massime associate, come
potrebbe essere previsto. Se vuoi caricare immagini ad alto DPI e il rapporto tra pixel del dispositivo supera una soglia, ecco cosa puoi fare:
#my-image { background: (low.png); }
@media only screen and (min-device-pixel-ratio: 1.5) {
#my-image { background: (high.png); }
}
La situazione si complica un po' con tutti i prefissi dei fornitori, soprattutto a causa delle enormi differenze di posizionamento dei prefissi "min" e "max":
@media only screen and (min--moz-device-pixel-ratio: 1.5),
(-o-min-device-pixel-ratio: 3/2),
(-webkit-min-device-pixel-ratio: 1.5),
(min-device-pixel-ratio: 1.5) {
#my-image {
background:url(high.png);
}
}
Con questo approccio, puoi recuperare i vantaggi dell'analisi anticipata, che è andata persa con la soluzione JS. Inoltre, hai la flessibilità di scegliere i punti di interruzione adattabili (ad esempio, puoi avere immagini con DPI bassi, medi e alti), che non era possibile con l'approccio lato server.
Purtroppo è ancora un po' complicato e genera CSS dall'aspetto strano (o richiede l'elaborazione preliminare). Inoltre, questo approccio è limitato alle proprietà CSS, quindi non è possibile impostare un <img src>
e le immagini devono essere tutte elementi con uno sfondo. Infine, se ti basi esclusivamente sul rapporto pixel del dispositivo, potresti trovarti in situazioni in cui lo smartphone ad alta risoluzione scarica una risorsa immagine di grandi dimensioni con un'elevata risoluzione su una connessione EDGE. Non è la migliore esperienza utente.
Utilizzare le nuove funzionalità del browser
Di recente si è parlato molto del supporto della piattaforma web per il problema delle immagini ad alto DPI. Di recente Apple ha fatto il suo ingresso in questo spazio, portando la funzione CSS image-set() in WebKit. Di conseguenza, sia Safari sia Chrome lo supportano. Poiché si tratta di una funzione CSS, image-set()
non risolve il problema per i tag <img>
. Inserisci
@srcset, che risolve il problema, ma (al momento di
scrivere questo articolo) non ha ancora implementazioni di riferimento. La sezione successiva approfondisce image-set
e srcset
.
Funzionalità del browser per il supporto di DPI elevati
In definitiva, la decisione sull'approccio da adottare dipende dai tuoi requisiti specifici. Detto questo, tieni presente che tutti gli approcci
precedenti presentano degli svantaggi. Tuttavia, in futuro, quando image-set
e srcset saranno ampiamente supportati, saranno le soluzioni appropriate a questo problema. Per il momento, parliamo di alcune best practice che possono avvicinarci il più possibile a questo futuro ideale.
Innanzitutto, quali sono le differenze tra i due? image-set()
è una funzione CSS, appropriata per l'utilizzo come valore della proprietà CSS background.
srcset è un attributo specifico per gli elementi <img>
, con sintassi simile.
Entrambi questi tag ti consentono di specificare dichiarazioni di immagini, ma l'attributo srcset consente anche di configurare l'immagine da caricare in base alle dimensioni della visualizzazione.
Best practice per gli image-set
La funzione CSS image-set()
è disponibile con il prefisso -webkit-image-set()
. La sintassi è abbastanza semplice: accetta una o più dichiarazioni di immagini separate da virgola, costituite da una stringa URL o dalla funzione url()
seguita dalla risoluzione associata. Ad esempio:
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
In questo modo il browser viene informato che sono disponibili due immagini tra cui scegliere. Uno è ottimizzato per i display 1x e l'altro per i display 2x. Il browser può quindi scegliere quale caricare in base a una serie di fattori, che potrebbero includere anche la velocità della rete, se è abbastanza intelligente (non attualmente implementato, per quanto ne so).
Oltre a caricare l'immagine corretta, il browser la ridimensiona anche di conseguenza. In altre parole, il browser presume che le immagini 2x siano il doppio delle immagini 1x e, di conseguenza, ridurrà l'immagine 2x di un fattore 2, in modo che l'immagine abbia le stesse dimensioni sulla pagina.
Anziché specificare 1x, 1,5x o Nx, puoi anche specificare una determinata densità di pixel del dispositivo in dpi.
Questo metodo funziona bene, tranne nei browser che non supportano la proprietà image-set
, che non mostrerà alcuna immagine. Questo è chiaramente un problema, quindi
devi utilizzare un piano di riserva (o una serie di piani di riserva) per risolverlo:
background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
Also include other prefixed versions of this */
background-image: image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x
);
Il codice riportato sopra caricherà l'asset appropriato nei browser che supportano
l'attributo image-set e, in caso contrario, tornerà all'asset 1x. L'ovvio svantaggio è che, sebbene il supporto del browser image-set()
sia ridotto, la maggior parte degli user agent riceverà la risorsa 1x.
Questa demo utilizza image-set()
per caricare l'immagine corretta, passando all'asset 1x se questa funzione CSS non è supportata.
A questo punto, potresti chiederti perché non utilizzare semplicemente il polyfill (ovvero creare uno shim JavaScript per) image-set()
e chiudere la questione. A quanto pare, è abbastanza difficile implementare polyfill efficienti per le funzioni CSS. Per una spiegazione dettagliata del motivo, consulta questa discussione su www-style.
Srcset immagine
Ecco un esempio di srcset:
<img alt="my awesome image"
src="banner.jpeg"
srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">
Come puoi vedere, oltre alle dichiarazioni x fornite da image-set
,
l'elemento srcset accetta anche i valori w e h che corrispondono alle
dimensioni della visualizzazione, tentando di pubblicare la versione più pertinente. Il codice sopra riportato mostrerà banner-phone.jpeg ai dispositivi con una larghezza della visualizzazione inferiore a 640 px, banner-phone-HD.jpeg ai dispositivi con DPI elevati e schermo di piccole dimensioni, banner-HD.jpeg ai dispositivi con DPI elevati e schermo di dimensioni superiori a 640 px e banner.jpeg a tutti gli altri dispositivi.
Utilizzo di image-set per gli elementi immagine
Poiché l'attributo srcset degli elementi img non è implementato nella maggior parte dei browser, potrebbe essere allettante sostituire gli elementi img con <div>
con sfondi e utilizzare l'approccio image-set. Funziona, ma con alcune limitazioni. Lo svantaggio è che il tag <img>
ha un valore semantico
a lungo termine. In pratica, questo è importante soprattutto per i crawler web e per motivi di accessibilità.
Se finisci per utilizzare -webkit-image-set
, potresti essere tentato di utilizzare la proprietà CSS background. Lo svantaggio di questo approccio è che devi specificare le dimensioni dell'immagine, che sono sconosciute se utilizzi un'immagine non 1x.
Invece, puoi utilizzare la proprietà CSS content come segue:
<div id="my-content-image"
style="content: -webkit-image-set(
url(icon1x.jpg) 1x,
url(icon2x.jpg) 2x);">
</div>
L'immagine verrà ridimensionata automaticamente in base a devicePixelRatio. Consulta
questo esempio della tecnica sopra descritta in azione,
con un'opzione di riserva aggiuntiva per url()
per i browser che non supportano
image-set
.
Polyfill di srcset
Una funzionalità utile di srcset
è che è dotato di un valore predefinito.
Se l'attributo srcset non è implementato, tutti i browser devono elaborare l'attributo src. Inoltre, poiché si tratta solo di un attributo HTML, è possibile creare polyfill con JavaScript.
Questo polyfill è dotato di test di unità per garantire che sia il più possibile conforme alla specifica. Inoltre, sono stati implementati controlli che impediscono al polyfill di eseguire codice se srcset è implementato in modo nativo.
Ecco una demo del polyfill in azione.
Conclusione
Non esiste una soluzione magica per risolvere il problema delle immagini ad alto DPI.
La soluzione più semplice è evitare del tutto le immagini, optando invece per SVG e CSS. Tuttavia, non è sempre realistico, soprattutto se hai immagini di alta qualità sul tuo sito.
Gli approcci in JS, CSS e l'utilizzo lato server hanno tutti i loro punti di forza
e di debolezza. L'approccio più promettente, tuttavia, è sfruttare le nuove funzionalità del browser. Anche se il supporto del browser per image-set
e srcset
è ancora incompleto, esistono soluzioni alternative ragionevoli da utilizzare oggi.
Per riepilogare, i miei consigli sono i seguenti:
- Per le immagini di sfondo, utilizza image-set con le alternative appropriate per i browser che non lo supportano.
- Per le immagini dei contenuti, utilizza un polyfill srcset o ricorre all'utilizzo di image-set (vedi sopra).
- Per le situazioni in cui sei disposto a sacrificare la qualità delle immagini, valuta la possibilità di utilizzare immagini 2x molto compresse.