In vista del rilascio di un sito con layout responsive ho cercato una soluzione che mi permettesse di ottimizzare il caricamento delle immagini. Alla base del ragionamento c’è una semplice considerazione: se la risoluzione del device è di 480 pixel è inutile fornire immagini 3 volte più grandi, soprattutto se l’utente non ha a disposizione una connessione a banda larga.
Ho immaginato questa soluzione basandomi su WordPress, dove è semplice avere in automatico diversi tagli della stessa foto. Il limite più grosso della tecnica proposta, quindi, è dare per scontata l’esistenza di diverse versione dello stesso file. A differenza di Adaptive Images non è prevista la creazione di immagini “on the fly”.
Potete vedere la demo [1] accedendo con vari dispositivi se potete.
Prima di passare all’analisi del codice alcune considerazioni importanti.
– Il proof of concept utilizza un cookie, un javascript da 1,1 KB (compresso) e 2 regole da inserire in un file htaccess (se avete accesso alla configurazione del virtualhost, ovviamente, mettete le regole lì dentro. Le elaborazioni saranno più veloci).
– Lo script è volutamente in javascript puro per questioni prestazionali. La prima versione che ho fatto usava jQuery ma, anche se l’adoro, è pur sempre un wrapper e quindi più lento nell’esecuzione. Al primo caricamento, in molti casi, non venivano fornite le immagini giuste per via di una lenta elaborazione. Dopo che la libreria veniva servita dalla cache del browser, ovviamente, la situazione cambiava.
– L’approccio che ho utilizzato inizialmente era di impostare come default le immagini più piccole, per sostituirle all’occorrenza con quelle più grandi.
Dopo tutti i test effettuati ho valutato che fosse meglio agire al contrario. In caso di errori da parte del client il massimo che può accadere è che gli vengano fornite le immagini di peso maggiore; poco male rispetto al caso in cui ad un browser con risoluzione di 1900×1200 vengano mostrate foto 70×70.
– Questa tecnica non si basa sull’analisi degli user-agent ma sulla risoluzione dello schermo dell’utente indipendentemente dal dispositivo che usa. Non era mia intenzione fornire un’immagine per ogni device ma, piuttosto, ragionare per breakpoint (anche abbastanza ampi).
Mi spiego meglio: nella maggior parte dei casi la differenza tra l’immagine desktop e quella per tablet potrebbe essere minima; quello che è importante è riuscire a distinguere le foto tra smartphone e pc. Ecco perchè i breakpoint per default sono 3: small, medium e normal.
Attenzione ad un particolare: il javascript utilizza window.screen.availWidth. Ciò significa che sul mio tablet android, sia inversione portrait che landscape, il valore restituito sarà sempre 1280, ovvero la risoluzione massima impostata. In sostanza do per scontate che se entrate nel sito con una risoluzione la manterrete per tutta la navigazione.
La scelta di comprire con un unico breakpoint tutte le versioni tablet è voluta anche per via di possibili problemi di cache del cookie. Molto dipende anche dai breakpoint che avete costruito voi nel vostro layout responsive.
Passiamo al codice.
In testa alla pagina (subito dopo il tag <html>) inseriamo il richiamo al file “responsive.js”:
<script type="text/javascript" src="responsive.js">
La posizione di inserimento non è scelta a caso. Per evitare possibili rallentamenti è necessario che il codice sia eseguito il prima possibile. Se così non fosse, nei casi peggiori, al primo caricamento della pagina verrebbero restituite le immagini di dimensioni maggiori anche nei casi di dispositivi mobili.
Il file contiene un array da personalizzare. Sono i breakpoints, ovvero le risoluzioni da gestire. Il primo valore copre tutti i dispositivi larghi fino a 480 pixel (sostanzialmente gli smartphone nei due orientamenti). “small” è il valore che verrà scritto nel cookie. Il secondo valore copre tutti gli schermi fino a 1280 (anche desktop). “medium” è il valore che verrà scritto nel cookie. Potete aggiungere quante coppie chiave/valore volete.
Ho volutamente inserito dei messaggi “console.log” a scopo informativo. Vanno eliminati per gli ambienti di produzione.
var breakpoints = [["480", "small"], ["1280", "medium"]];
var u_res = window.screen.availWidth;
function readCookie(name) {
var n_cookie = document.cookie.split("; "),
count = n_cookie.length, single_cookie;
while (count--) {
single_cookie = n_cookie[count].split("=");
if (single_cookie[0] === name)
return single_cookie[1];
}
return false;
}
// cancello cookie on resize finestra
window.onresize = function(event) {
document.cookie = "s_responsive=; path=/;expires=Sun, 15 Aug 1976 00:00:01 GMT;";
}
// se la max risoluzione utente supera l'ultimo breakpoint non entro in loop e cancello eventuale cookie
if (u_res > breakpoints[breakpoints.length-1][0]) {
if (window.console) console.log('la risoluzione supera l\'ultimo breakpoint. quindi viene fornita immagine default');
if (readCookie('s_responsive') != '') {
document.cookie = "s_responsive=; path=/;expires=Sun, 15 Aug 1976 00:00:01 GMT;";
if (window.console) console.log('cookie responsive presente e cancellato');
}
} else {
for(var i = 0; i < breakpoints.length; i++) {
if (window.console) console.log('inizio loop ' +i);
if (u_res <= breakpoints[i][0]) {
if (readCookie('s_responsive') != breakpoints[i][1]) {
document.cookie = "s_responsive=" + breakpoints[i][1] + "; path=/;";
if (window.console) console.log('cookie settato a: ' +breakpoints[i][1]);
}
if (window.console) console.log('max risoluzione è: ' +breakpoints[i][0]);
if (window.console) console.log('fine loop ' +i);
break;
}
if (window.console) console.log('fine loop ' +i);
}
}
A questo punto creiamo un htaccess con queste regole:
RewriteEngine On
RewriteCond %{HTTP_COOKIE} s_responsive=medium [NC]
RewriteCond %{QUERY_STRING} ^m=(.*)&s=(.*)$
RewriteRule ^(.*)/(.*)\.(jpe?g|png|gif)$ $1/%1 [NC,L]
RewriteCond %{HTTP_COOKIE} s_responsive=small [NC]
RewriteCond %{QUERY_STRING} ^m=(.*)&s=(.*)$
RewriteRule ^(.*)/(.*)\.(jpe?g|png|gif)$ $1/%2 [NC,L]
Il numero delle regole e il valore del cookie dovrebbe rispecchiare quanto scritto nel javascript.
Infine le immagini saranno così richiamate:
<img src="img/test.normal.png?m=test.medium.png&s=test.small.png" alt="" />
Se aggiungete nuovi tagli e parametri in GET ricordatevi di aggiornare il file htaccess ovviamente.
A mio parere un sito responsive non dovrebbe limitarsi solamente alla presentazione ma anche all’ottimizzazione del codice. Adattare la struttura della pagina semplicemente nascondendo gli elementi o forzando le dimensioni delle immagini equivale a nascondere la polvere sotto al tappeto.
Ipotizziamo una pagina adattiva ben fatta, il cui peso finale sia di circa 1,2 mega (non è così tanto se pensiamo che diversi studi [2] confermano l’aumento del peso medio dei siti). Il file in questione si vedrà sicuramente bene sui vari schermi ma in tutti i casi i dati scaricati saranno sempre 1,2 mega.
Premesso che una soluzione lato server “completa” è sempre auspicabile (vedi ad esempio wurfl [3]), non sempre è possibile applicarla: mancanza di competenze o limiti architetturali i casi più comuni.
Il mio obiettivo era trovare una soluzione performante, semplice nel markup e alla portata di tutti (o quasi). Mission: Impossible? Può darsi ma alla fine sono riuscito a creare un proof of concept che forse vale la pena approndire.
In rete esistono già moltissime soluzioni che sfruttano javascript per servire al client le immagini adatte. Alcune sono eccellenti, altre un po’ macchinose. Nella maggior parte dei casi viene eseguito un loop sui tag img presenti in pagina e, in base al device in uso, si modifica il nome del file da richiedere al server.
Altri script richiedono l’aggiunta di markup aggiuntivo: attributi data, tag span, div, noscript. In diversi casi per una semplice immagine vengono richieste 10 linee di codice. Un po’ troppo per i miei gusti.
In definitiva, con l’approccio javascript, si scarica sul client l’ònere di effettuare il calcolo per sapere quale immagine richiedere al server.
Se non avete imbottito la vostra pagina di codici che manipolano il DOM non dovreste notare un aggravio delle prestazioni. Ma nel caso di una pagina ricca di immagini e di uno smartphone di fascia medio-bassa con connessione 3G cosa potrebbe accadere? Dai test che ho effettuato ci sono stati dei rallentamenti nel caricamento della pagina. I fattori da tenere in considerazione sono diversi: potenza di calcolo del processore, numero di immagini presenti, numero di javascript presenti in pagina etc etc.
Per ottimizzare le operazioni da compiere si potrebbe agganciare la sostituzione delle immagini responsive ad un plugin di lazy load [4]. Così facendo, almeno, con un solo loop si otterebbe un doppio vantaggio.
La soluzione lato server (o quasi) più famosa, probabilmente, è Adaptive Images [5]. Senza entrare nello specifico: una pagina php funge da proxy per tutte le immagini. Prima di fornire la foto richesta viene valutato il device e, in base alle sue caratteristiche, viene restituito il file corretto presente nella cache del server. Se l’immagine non esiste viene creata dallo script stesso. Con questa tecnica si usa un cookie, php e delle regole impostate sull’.htaccess.
L’idea di base è ottima e non richiede particolari conoscenze. Ciò che non mi ha entusiasmato è il fatto che tutte le richieste passino per un singolo file php. Periodicamente lo stesso script si deve anche preoccupare di creare il file di cache. Ragionando su siti ad alto traffico e con immagini di una certa qualità questa soluzione è, a mio avviso, abbastanza onerosa in termini di carico.
Vi rimando a questo file [6] per una lista (non esaustiva) di soluzioni per gestire le immagini responsive sia lato client che server.
A questo punto ho raccolto abbastanza informazioni per fissare i punti dell’obiettivo. Volevo una soluzione che fosse:
– leggera per poter essere gestita anche da device poco potenti
– semplice da implementare senza troppo markup aggiuntivo
– sviluppata senza loop lato client sulle immagini
– funzionante senza controllo dello user-agent
Ho effettuato una serie di test con i device a mia disposizione ma se anche voi avete modo di fare delle prove sarò lieto di leggere i vostri commenti