Una delle caratteristiche del panorama attuale dei dispositivi è che è disponibile una gamma molto ampia di densità di pixel dello schermo. Alcuni dispositivi sono dotati di display ad altissima risoluzione, mentre altri rimangono indietro. Gli sviluppatori di applicazioni devono supportare una gamma di densità di pixel, il che può essere piuttosto impegnativo. Sul web mobile, le difficoltà sono aggravate da diversi fattori:
- Grande varietà 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 illustrerà alcune tecniche utili per eseguire questa operazione 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 (punti per pollice).
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. Nel 2008, gli smartphone a 150 dpi erano la nuova normalità. La tendenza all'aumento della densità del display è proseguita e i nuovi smartphone di oggi sono dotati di display a 300 dpi (brandizzati "Retina" di Apple).
Il Santo Graal, naturalmente, è un display in cui i pixel sono completamente invisibili. Per quanto riguarda il fattore di forma degli smartphone, l'attuale generazione di display Retina/HiDPI potrebbe avvicinarsi a quella ideale. Tuttavia, nuove classi di hardware e indossabili come Project Glass continueranno probabilmente a incrementare la densità dei 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 è stato progettato il web, 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 della grande variazione nelle dimensioni e nella densità degli schermi, era necessario un metodo standard per fare in modo che le immagini venissero visualizzate correttamente su diverse densità e dimensioni.
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 in pixel fisici 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 con 28 pollici 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 fisici 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). L'iPhone e l'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 le proporzioni rotonde possono essere migliori è che possono portare a meno artefatto di pixel secondari.
Tuttavia, la realtà del panorama dei dispositivi è molto più varia e gli smartphone Android spesso hanno DPR pari a 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.
Approccio a immagine singola: usa una sola immagine, ma usala in modo 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 singolo dell'immagine:
- Immagine HiDPI molto compressa
- Formato dell'immagine assolutamente eccezionale
- 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à?
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 è peggio 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 in 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 sono costose da riavviare. Per un sito con molte immagini, l'approccio più efficace è mantenere attiva una singola connessione HTTP, riutilizzandola il più a lungo possibile. Se la connessione viene interrotta prematuramente perché è stata scaricata un'immagine 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 questo approccio poco pratico.
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 rientra nel caso d'uso "arture".
Utilizza 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 delle connessioni 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 si comportano come quelle descritte 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ù nell'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 il doppio della risoluzione su una connessione EDGE. Non è la migliore esperienza utente.
Utilizzare le nuove funzionalità del browser
Di recente si è parlato molto sul supporto delle piattaforme web
per il problema delle immagini con DPI elevato. 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 valori DPI elevati
In definitiva, la decisione su quale approccio adottare dipende dai tuoi requisiti specifici. Detto questo, tieni presente che tutti gli approcci
precedenti presentano degli svantaggi. In futuro, tuttavia, una volta che 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 il set di immagini
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
);
Ciò indica al browser che è possibile scegliere tra due immagini. Uno è ottimizzato per display 1x e l'altro per 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 ridimensionerà anche di conseguenza. In altre parole, il browser presuppone che due immagini siano due volte più grandi di un'immagine e quindi ridurrà l'immagine due volte di un fattore pari a 2, in modo che l'immagine appaia della stessa dimensione nella pagina.
Anziché specificare 1x, 1,5x o Nx, puoi anche specificare una determinata densità di pixel del dispositivo in dpi.
Questo 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
);
Quanto sopra carica l'asset appropriato nei browser che supportano
il set di immagini, altrimenti ricorrerà all'asset 1x. L'avvertenza ovvia è che, anche se il supporto del browser image-set()
è basso, la maggior parte degli user agent riceverà 1 volta l'asset.
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 elevato e schermo di piccole dimensioni, banner-HD.jpeg ai dispositivi con DPI elevato 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 negli elementi img non è implementato nella maggior parte dei browser, si potrebbe avere la tentazione di sostituire gli elementi img con <div>
con sfondo e di utilizzare l'approccio impostato sulle immagini. Funziona, ma con alcune limitazioni. Lo svantaggio in questo caso è che il tag <img>
ha un valore semantico
da lungo tempo. 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 di farlo, puoi utilizzare la proprietà CSS dei contenuti nel seguente modo:
<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 un punto magico per risolvere il problema delle immagini ad alta 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 del 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 riassumere, 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 di contenuti, utilizza un polifill srcset oppure utilizza utilizzando 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.