Blog
[En pratique] Comment j’ai généré des images pour le responsive design
- 15 novembre 2013
- Publié par : Guillaume Peyronnet
- Catégorie : technique
Bien que tout le monde s’accorde pour dire que le responsive design c’est fantastique car cela permet de facilement s’adapter à tous les écrans, toutes les tailles, on est encore loin d’avoir des solutions techniques parfaites pour le mettre en place. Le premier mirage est que pour s’adapter à tous les périphériques on a souvent une solution d’entre deux. Par exemple, pour transformer un menu horizontal en menu de sélection, on va insérer double code, et cacher via les media queries le menu qui n’est pas adapté. Résultat ? Le code source est plus lourd. Mais au niveau de l’utilisateur c’est tout de même plus pratique.
Bien sûr, quelques lignes de codes ne provoquent pas un gros goulot d’étranglement, non, c’est plutôt au niveau des images que le problème se pose : faire télécharger du 1280 pixels de large alors qu’on affiche du 50 pixels de large, cela fait une énorme différence en terme de temps de chargement, à tel point que cela influence l’expérience utilisateur (ceux qui ont essayé de consulter une galerie de photos dans le métro me comprendront).
Comment servir des images aux tailles optimales, quelles que soient les résolutions d’écrans des internautes ?
C’est exactement la problématique que j’avais sur un site, et que je souhaite partager avec vous.
Voici ma galerie de photos pour un article sur le jeu vidéo Battlefield 4 :
Mon code source est conçu pour que la largeur des images affichées dépende de la largeur de la colonne. Quand on redimensionne la fenêtre, le nombre de colonnes (5) est fixe, mais les images rapetissent de façon à s’adapter à la nouvelle largeur. La galerie affiche donc parfois les images en 280px de large, parfois en 25px, tout dépend de la résolution de l’écran. On peut dire que c’est responsive (certains me diront qu’il faudrait aussi réduire le nombre de colonnes, mais c’est un choix arbitraire de ne pas le faire).
Tout ça semble très bien, non ? Non, bien sûr que non ! Si la taille des images à l’écran varie, ce n’est pas le cas de la ressource elle-même. Peu importe que les images occupent 280px ou 25px de large, dans tous les cas le navigateur doit récupérer une ressource qui est bien plus large : une grosse image, lourde à charger, pour finalement ne l’afficher qu’en taille réduite. C’est à dire occuper de la bande passante pour pas grand chose. Autant dire que sur mobile, avec une connexion un peu limitée, la galerie met une éternité à se charger.
J’ai donc cherché des solutions pour pouvoir servir des ressources adaptées, c’est à dire des images de poids réduit, en fonction de la réalité de la résolution d’écran.
La première solution, la plus évidente je pense, est d’afficher les images en arrière plan d’un bloc, via le css, et de changer l’url de la ressource via les media queries. Ça marche. Mais c’est extrêmement lourd :
- il faut multiplier les lignes de codes dans le css, et je ne parle pas d’une ou deux lignes : j’ai parfois des galeries avec 50 images…
- il faut précalculer les images aux différentes tailles. C’est à dire, comme le proposent beaucoup de scripts tout faits, arbitrairement décider de 3 à 5 résolutions standards et générer les images en amont. La pré-génération ne pose pas de problème. Par contre, avoir un panel limité de résolutions fait que l’on n’est pour ainsi dire jamais sur le cas idéal. Soit je suis en dessous de la résolution requise, soit je suis au dessus. C’est de l’à peu près qui ne me semble pas robuste.
J’ai donc préféré me creuser la cervelle pour trouver une solution plus satisfaisante. En tout cas plus satisfaisante POUR MOI. En effet, c’est une solution maison, qui ne s’adaptera peut-être qu’à mon cas unique, mais qui me satisfait pleinement, vu ma configuration de serveur. Et si jamais cela peut vous donner des idées…
Place à l’action
Sur la machine, j’ai un serveur apache avec en front end un proxy nginx. Je vais utiliser un processus en plusieurs étape afin qu’une fois le système en place tout se fasse automatiquement : redimensionnement des images et affichage des ressources adaptées.
Je commence par modifier le code source de ma galerie d’images. Pour éviter de faire charger des images non adaptées (et donc occuper la bande passante inutilement), je vais afficher un gif transparent à la place des images, le tout en inline (j’évite ainsi de faire un appel extérieur inutile par image). Mais comme j’ai tout de même besoin d’avoir l’url de la ressource, je vais placer un attribut data-src indiquant l’url de l’image pleine résolution.
[cc lang=”html5″][/cc]
Ensuite, je vais utiliser une couche javascript pour qu’après le chargement de la page l’attribut src de la page soit remplacé par une url générée à la volée qui contient des indications sur l’espace occupée par le gif transparent (=la dimension réelle d’affichage dans le navigateur). Le script génère ce genre de chose :
[cc lang=”html5″][/cc]
J’appelle donc une image qui n’existe pas ??!!
C’est là où est le “trick”. L’url demandée va passer par le serveur nginx qui va traiter la demande de façon “astucieuse” : quand l’image n’existe pas nginx va faire passer l’info pour qu’elle soit générée. Si elle existe, nginx va la servir :
[cc lang=”apache”]
location ~ ^/images/(.*)-size(d+)x(d+).jpg$ {
root /home/cache/image ;
add_header Cache-Control public;
add_header Link “<$scheme://www.example.org/images/big/$1.jpg>; rel=”canonical””;
try_files /$2/$1.jpg @proxy ;
}
location @proxy{
proxy_pass http://127.0.0.1:8080;
}
[/cc]
Si le fichier image optimisé existe sur le serveur, try_files le trouve et le sert directement.
Si le fichier n’existe pas, alors c’est @proxy qui est appelé.
@proxy se contente d’envoyer la requête à travers le proxy, et donc c’est apache qui prend la relève.
Du côté d’Apache
On utilise une règle de réécriture dans le .htaccess du site :
[cc lang=”apache”]
RewriteEngine On
RewriteRule ^images/(.*)-size([0-9]+)x([0-9]+).jpg$ /process.php?titre=$1&w=$2&h=$3 [L]
[/cc]
La règle de réécriture permet l’exécution de process.php avec les paramètres de taille et le nom de l’image.
[cc lang=”php”]
$_GET[‘titre’] = preg_replace(‘/[^-a-zA-Z0-9_]/’, ”, $_GET[‘titre’]);
$w = preg_replace(‘/[^-a-zA-Z0-9_]/’, ”, $w);
if($w>900) $w = 900; /*On ne va jamais plus loin que 900px de largeur*/
$token = md5(uniqid());
exec(‘mkdir ‘.escapeshellarg(/home/cache/image/’.$w));
$w2 = round($w*0.95); /*On crée une image légèrement plus petite que la taille optimale : la différence ne devrait pas se voir à l’écran et on réduit le poids*/
exec(‘cp ‘.escapeshellarg(‘/var/www/site/images/’.$_GET[‘titre’].’.jpg’).’ /tmp/’.$token.’.jpg’);
exec(escapeshellcmd(‘mogrify -strip -resize ‘.$w2.’x /tmp/’.$token.’.jpg’)); /*On peut optimiser davantage notamment en changeant le taux de compression*/
exec(‘mv /tmp/’.$token.’.jpg ‘.escapeshellarg(/home/cache/image/’.$w.’/’.$_GET[‘titre’].’.jpg’));
header(“Content-Type: image/jpeg”);
readfile(‘/home/cache/image/’.$w.’/’.$_GET[‘titre’].’.jpg’);
[/cc]
Le script, en substance, crée un double de l’image originale aux bonnes dimensions (via mogrify qui est une application installée sur le serveur – faire le redimensionnement via php serait une mauvaise idée en terme de performances), et sert l’image à l’internaute.
Bien sûr, si on faisait l’opération à chaque appel de l’image, ce serait désastreux niveau performance. Mais grâce au stockage des variantes de l’image, le prochain appel à la même résolution d’un internaute se contente d’atteindre nginx, sans jamais atteindre apache. On y gagne.
Voilà, avec cette solution maison, je sers toujours des images aux dimensions optimales. Certes, ça prend de l’espace disque, puisqu’on peut avoir plusieurs centaines d’images pour une seule ressource, mais la bande passante de l’internaute est, elle, sauvegardée.
Mais, mon SEO ne va-t-il pas en prendre un coup ?
Pour ne pas prendre de risque au niveau du référencement web, il est prudent d’ajouter quelques subtilités :
- toujours avoir un lien qui pointe vers la ressource HQ
- configurer le serveur pour qu’il renvoie un header canonical pointant vers la version HQ quand on interroge les variantes de l’image HQ [edit : 20/11/2013 – Ajout du code pour le header canonical dans la configuration nginx]
Et pour aller plus loin ?
Un seul mot : lazyload.