Adaptive Images et Responsive Web Design

Openweb.eu.org > Articles  > Adaptive Images et Responsive Web Design

Abstract

Cédric Morin étudie une solution prête à l’emploi pour résoudre le casse-tête des Adaptive Images, adaptée aux sites dynamiques (une de plus, oui).

Cette solution est complexe, mais est prévue pour être complètement automatisable (comprenez industrialisable).

Article

Note : cet article a été initialement publié sur le Blog de Nursit, nous le remettons ici avec l’aimable autorisation de son auteur.

Adaptive Images, que l’on pourrait traduire par Images adaptatives, désigne la pratique qui vise à adapter les taille, résolution et qualité des images utilisées dans le contenu éditorial d’une page web en fonction de l’utilisateur.

Les images adaptatives sont nécessaires pour les sites Responsive qui vont adapter leur affichage au périphérique de consultation [1] : sur un site Responsive la même page est rendue de manière différente selon la taille de l’écran.

Adapter les images vise avant tout à améliorer l’expérience utilisateur, en délivrant l’image qui convient le mieux : inutile d’envoyer une image trop grande et trop lourde si l’utilisateur consulte la page sur un smartphone avec un petit écran et une mauvaise connexion internet !

Le champ des images adaptatives regroupe plusieurs cas d’usage [2] :

  • Viewport Switching : adapter la taille de l’image à celle du viewport, par exemple en envoyant une image de lageur 320px sur un smartphone et une image de largeur 800px sur une tablette ;
  • Device-pixel ratio (DPR) Switching : adapter l’image à la résolution de l’écran, par exemple en envoyant une image avec 4 fois plus de pixels à ceux qui visualisent la page sur écran Retina ;
  • Network Switching : adapter l’image à la qualité de la connexion, par exemple en envoyant une image de qualité réduite si l’utilisateur est connecté en Edge dans le métro ;
  • Art Direction : adapter l’image à la taille affichée, par exemple en envoyant une image recadrée autour du sujet important si elle doit s’afficher sur un petit écran plutôt qu’une version redimensionnée de l’image sur laquelle le sujet important sera difficilement visible.

Depuis quelques années, de nombreuses solutions émergent mais toutes ont leurs défauts et imperfections, et en choisir une s’avère compliqué. Fondamentalement, il manque encore une API et un markup HTML dédié à cet usage, et chacun essaye de faire au mieux avec ce qui existe.

A ce jour rien n’est encore figé et les discussions du W3C ont longtemps tourné autour de nouvelles balises <picture> et <source> ainsi que d’attributs media et srcset, mais il semble que l’attribut srcN tienne maintenant la corde. Il se passera encore quelques années avant que tout cela soit normalisé et disponible et utilisable sur une proportion majoritaire du parc des navigateurs.

Pourtant, c’est dès maintenant qu’on a besoin de cette technique, pour nos sites Responsive.

Je me suis donc mis à la recherche, parmi les solutions existantes, de celle qui serait pertinente à utiliser dès aujourd’hui dans un CMS [3], ce qui permet une certaine forme d’automatisation dans la génération du markup.

Images Adaptatives Idéales

Toutes les solutions connues sont imparfaites et ont leurs inconvénients. En les examinant une par une on en arrive à construire une spécification assez claire de ce que l’on voudrait obtenir.

Il y a ce à quoi on tient vraiment :

  • conserver la sémantique de la balise <img/>, notamment pour des raisons d’accessibilité ;
  • conserver un seul hit par image en toutes circonstances ;
  • avoir une unicité de contenu par URL, pour permettre la mise en cache (Webperf) ; cela vaut pour la page HTML, mais aussi pour les images ;
  • ne pas dégrader le rendu en l’absence de JavaScript, si possible en continuant à servir des images adaptées au périphérique ;
  • diminuer le poids des contenus téléchargés sur les navigateurs mobiles avec un petit écran ;
  • augmenter la qualité des images téléchargées par les navigateurs sur les écrans à haute résolution (dpi>=1.5x) ;
  • adapter la qualité des images téléchargées à la qualité de la connexion.

Et ce qu’on aimerait bien avoir en plus :

  • un rendu progressif lorsque la connexion est mauvaise et que les images sont longues à télécharger ;
  • et bien sur, un outil qui se charge de tout cela de manière transparente.

Enfin, cerise sur le gâteau, on voudrait bien éviter

  • de faire de la détection de User-Agent (c’est le mal et a future fail strategy) ;
  • de servir des images à l’aide d’un script dynamique côté serveur : utiliser un script PHP [*] pour envoyer une image est beaucoup plus couteux pour la charge des serveurs que lorsqu’Apache [*] envoie directement un fichier statique (écologie des serveurs)

Voila un beau programme, et il faudra sûrement faire des concessions sur certains points…

La technique Clown-cars

J’ai beaucoup aimé l’idée présentée par Estelle Weyl, qu’elle a appelée Clown-cars Technique [4] [5], basée sur un conteneur SVG, car elle ne repose pas sur JavaScript contrairement à la plupart des autres méthodes existantes.

J’étais donc parti pour implémenter cette solution, malgré quelques défauts identifiés de la méthode, liée à son accessibilité et au non-support de SVG par IE<=8 et Android<=2.3.

Mes tests ont été un peu laborieux, et mon premier prototype en place, j’ai constaté 2 points problématiques supplémentaires :

  • sur mon smartphone iOS, je constatais un bug de proportion incorrecte des images rendues [6]
  • j’ai réalisé que l’affichage sans style de la page ne rendait aucune image, les styles du conteneur SVG étant eux aussi inactifs.

Cela faisait à mon goût trop de défauts pour cette technique (dont le principal est la suppression de la balise <img/>) qui fut néanmoins une fausse piste inspirante.

Technique des 3 couches

Pas de miracle : si l’on veut que notre solution fonctionne sans JavaScript il faut se reposer sur CSS. En cela on resterait donc dans l’esprit de la technique Clown-cars.
En cherchant dans cette direction, j’ai aussi trouvé 2 autres pistes intéressantes :

  • l’une est une proposition d’extension de CSS3 pour modifier l’attribut src de la balise <img/>, mais qui ne marche (partiellement) que sous Webkit, ce qui la rend inutilisable en pratique ;
  • l’autre est l’utilisation de background multiples pour offrir un rendu progressif : une sorte de lowsrc remis au goût du jour, mais qui perd là aussi la sémantique de la balise <img/>.

Après quelques expérimentations, j’en suis arrivé à une sorte de fusion de ces 3 concepts, reposant sur l’empilement de 3 couches techniques et 3 couches visuelles qui vont fonctionner par enrichissement progressif.
C’est donc assez logiquement que je l’ai baptisée technique des 3 couches.

Couche 1 : HTML

Le HTML fournit l’information sous forme structurée. On veut donc une balise <img/>, qui porte le texte alternatif et propose un aperçu basse définition qui se suffit à lui même.
On choisit donc de fournir une image JPG très fortement comprimée, encodée en DATA URI [7]
L’image est encapsulée dans un wrapper non sémantique <span> qui porte un étiquette sous forme de classe c-xxx [8] :

  1. <span class="adapt-img-wrapper c-1841222306 jpg">
  2. <img src="data:image/jpeg;base64,..."
  3. width="770" height="1027"
  4. alt="Des casiers multicolores"
  5. class="adapt-img c-1841222306" />
  6. </span>

Télécharger

On respecte la sémantique de l’image, elle porte son texte alternatif, et elle fournit une prévisualisation de basse qualité qui se suffit à elle-même (c’est elle qui s’affichera aussi dans un rendu de la page sans style).

Couche 2 : CSS et media-queries

C’est ici qu’on va définir l’image finale que l’on doit afficher en fonction de la taille de la page, à l’aide de media-queries et de directives CSS dans une balise <style>.

D’abord, on fixe quelques styles génériques sur le wrapper <span> et son image :

  • pendant le chargement, l’image de pré-visualisation est affichée avec une opacité de 70% ;
  • l’image sera affichée en background du wrapper <span>
  • ainsi qu’en background de span:after, en superposition au dessus de l’image de prévisualisation.

J’ai retenu la valeur de 70% d’opacité pour la pré-visualisation, et c’est un compromis qui permet :

  • de voir suffisamment bien l’image ;
  • de simuler une transparence partielle dans le cas où l’image d’origine avait un fond transparent ;
  • de matérialiser qu’un processus de chargement est en cours.
  1. img.adapt-img {
  2. opacity:0.70;
  3. max-width:100%;
  4. height:auto;
  5. }
  6. span.adapt-img-wrapper,span.adapt-img-wrapper:after {
  7. display:inline-block;
  8. max-width:100%;
  9. position:relative;
  10. background-size:100%;
  11. }
  12. span.adapt-img-wrapper:after {
  13. position:absolute;
  14. top:0;
  15. left:0;
  16. right:0;
  17. bottom:0;
  18. content:"";
  19. }

Télécharger

On complète par des styles propres à chaque image et qui vont définir quelle image afficher en fonction de la taille du viewport. Par exemple ici, avec 2 breakpoints à 320px et 640px :

  1. @media screen and (max-width:320px) {
  2. span.c-1841222306, span.c-1841222306:after {
  3. background-image:url(local/cache-vignettes/L320xH427/dsc00037-2-r90-9a18c.jpg);
  4. }
  5. }
  6. @media screen and (max-width:640px) {
  7. span.c-1841222306, span.c-1841222306:after {
  8. background-image:url(local/cache-vignettes/L640xH854/dsc00037-2-r90-c3b69.jpg);
  9. }
  10. }
  11. @media screen and (min-width:641px) {
  12. span.c-1841222306, span.c-1841222306:after {
  13. background-image:url(local/cache-vignettes/L770xH1027/dsc00037-2-r90-4c4b5.jpg);
  14. }
  15. }

Télécharger

On peut aussi prendre en charge la résolution de l’écran en ajoutant des media-queries. Par exemple pour les résolutions de 320px et moins, on ajoute ici la prise en charge des densité 1.5x et 2x :

  1. @media screen and (-webkit-min-device-pixel-ratio: 1.5) and (max-width:320px), screen and (min--moz-device-pixel-ratio: 1.5) and (max-width:320px), screen and (min-resolution: 1.5dppx) and (max-width:320px) {
  2. span.c-1841222306, span.c-1841222306:after {
  3. background-image:url(local/cache-gd2/08004c1b4d333fc1e36e581f2a1ebd7a.jpg);
  4. }
  5. }
  6. @media screen and (-webkit-min-device-pixel-ratio: 2) and (max-width:320px), screen and (min--moz-device-pixel-ratio: 2) and (max-width:320px), screen and (min-resolution: 2dppx) and (max-width:320px) {
  7. span.c-1841222306, span.c-1841222306:after {
  8. background-image:url(local/cache-gd2/6dff337b451810af4be6b07176daed06.jpg);
  9. }
  10. }

Télécharger

Toutes ces directives CSS sont insérées dans une balise <style> dans la page HTML [9].
Lorsque la directive CSS est interprétée et que l’image cible (en fonction de notre écran et de sa résolution) est chargée, celle-ci vient se placer au dessus de l’image de prévisualisation, et également en dessous. On a superposé là aussi 3 couches, dont seule celle du dessus est visible (sauf en cas de transparence dans l’image).

Couche 3 : JavaScript

JavaScript va être utilisé pour finir le rendu : la superposition des 3 images pouvant générer des imperfections en cas de transparence, il convient de masquer les 2 couches du dessus. Pour cela on se branche sur window.onload [10] et on injecte des styles dans le DOM pour finir le rendu une fois les images chargées.

  1. <script type='text/javascript'>/*<![CDATA[*/
  2. var adaptImg_onload = function(){
  3. var sa = document.createElement('style');
  4. sa.type = 'text/css';
  5. sa.innerHTML = 'html img.adapt-img{opacity:0.01}html span.adapt-img-wrapper:after{display:none;}';
  6. var s = document.getElementsByTagName('style')[0];
  7. s.parentNode.insertBefore(sa, s);};
  8. function addLoadEvent(func){
  9. var oldol=window.onload;
  10. if (typeof oldol != 'function'){window.onload=func;}
  11. else{window.onload=function(){if (oldol){oldol();} func();}}
  12. }
  13. addLoadEvent(adaptImg_onload);
  14. /*]]>*/</script>

Télécharger

Dans le cas où JavaScript ne serait pas disponible, on peut insérer des styles complémentaires pour désactiver le rendu progressif sur les images qui peuvent avoir de la transparence (PNG et GIF) :

  1. <noscript><style type='text/css'>.png img.adapt-img,.gif img.adapt-img{opacity:0.01}span.adapt-img-wrapper.png:after,span.adapt-img-wrapper.gif:after{display:none;}</style></noscript>

On remarque que, dans les deux cas, span:after est complètement masqué par un display:none; alors que la balise <img/> est elle passée en opacité 1%.

Réparer Afficher l’image…
Cette astuce permet de rendre l’image visuellement transparente sans la masquer : elle reste affichée dans la page, elle peut prendre le focus et un clic droit de la souris fait bien apparaître les menus contextuels de type "Afficher l’image" ou "Enregistrer l’image". Néanmoins, c’est l’image de prévisualisation de basse qualité qui est alors affichée ou enregistrée.
On y remédie en ajoutant un attribut onmousedown sur la balise <img/> pour récupérer l’image affichée en background par l’élément parent [11] :

  1. <span class="adapt-img-wrapper c-1841222306 jpg">
  2. <img src="data:image/jpeg;base64,..."
  3. width="770" height="1027"
  4. alt="Des casiers multicolores"
  5. class="adapt-img c-1841222306"
  6. onmousedown="adaptImgFix(this)"
  7. />
  8. </span>

Télécharger

En définissant la fonction adaptImgFix :

  1. function adaptImgFix(n){var i=window.getComputedStyle(n.parentNode).backgroundImage.replace(/\W?\)$/,'').replace(/^url\(\W?|/,'');n.src=(i&&i!='none'?i:n.src);}

3 couches visuelles

En résumé, on a bien 3 couches visuelles :

  • au départ seule l’image de prévisualisation de la balise <img/> présente dans le HTML est visible : c’est la couche du milieu ;
  • ensuite CSS vient ajouter l’image adaptée par-dessus et par-dessous. Seule la couche du dessus est alors visible, sauf en cas de transparence de l’image qui provoque des imperfections en laissant voir la couche du milieu ;
  • enfin JS vient supprimer la couche du dessus, et mettre en transparence à 99% la couche du milieu : seule la couche du dessous qui contient l’image adaptée est alors visible.

Support dans les navigateurs

La méthode utilisée repose simplement sur les media-queries : le support est bon dans tous les navigateurs, à l’exception de 2 plateformes.

Internet Explorer

Internet Explorer ne supporte les DATA URI qu’à partir de IE 8 (avec une limite de 32ko dans le cas de IE lunettes de soleil et les media-queries qu’à partir de IE9 et IE 10 mobile.

On ne va donc pas se casser la tête : pas d’images adaptatives pour IE<10, que ce soit sur mobile ou desktop.

Comme IE10 ne prend plus en compte les commentaires conditionnels, il suffit donc d’envoyer l’image normale pour IE :

  1. <!--[if IE]><img src='local/cache-vignettes/L770xH1027/dsc00037-2-r90-4c4b5.jpg' width='770' height='1027' alt="Des casiers multicolores" class='adapt-img-fallback c-1841222306' /><![endif]-->
  2. <!--[if !IE]><!-->
  3. <span class="adapt-img-wrapper c-1841222306 jpg">
  4. <img src="data:image/jpeg;base64,..."
  5. width="770" height="1027"
  6. alt="Des casiers multicolores"
  7. class="adapt-img c-1841222306"
  8. onmousedown="imgFix(this)"
  9. />
  10. </span><!--<![endif]-->

Télécharger

Les utilisateurs d’Internet Explorer < 10 verront donc les images normales, dans la largeur maximum 1x, sans adaptation à l’écran. On se console du fait que cela ne concerne qu’un parc de périphériques mobiles vraiment très restreint.
Malheureusement ils auront tout de même une page HTML plus lourde du fait des images de prévisualisation embarquées. Tant pis, on ne peut pas faire mieux [12].

Android 2.x

On s’intéresse ici au parc de téléphones mobiles sous Android 2.x qui utilisent pour la plupart le navigateur par défaut.
Même si c’est un parc vieillissant et en déclin, on constate sur les serveurs de Nursit, que cela représente environ 10 fois plus d’utilisateurs que IE Mobile < 10 et environ 20% du parc Android. Pas question donc de les évacuer aussi simplement, d’autant plus qu’il n’y a pas de commentaires conditionnels ni de hack qui permettent de les cibler en particulier.

Il apparaît déjà que Android 2.x ne supporte la propriété CSS background-size que préfixée, on ajoute donc la propriété suivante dans les styles communs à toutes les images adaptatives :

  1. -webkit-background-size:100% 100%;

J’ai utilisé le service Mobitest pour valider le fonctionnement attendu de ma page. Quand j’ai testé le chargement sur un Nexus S Android 2.3, quelle ne fut pas ma surprise : au lieu de charger une image, celui-ci les chargeait toutes (ou presque) [13] !

A force d’essais-erreurs j’en suis parvenu à identifier 2 problèmes :

  • les surcharges CSS : en cas de surcharges CSS, Android 2.x charge les images de toutes les règles qui s’appliquent ;
  • le viewport à 800px : il semble que le téléphone de test réalisait systématiquement un premier rendu à 800px avant d’ajuster le viewport, provoquant 2 chargements d’image, même sans surcharge.

Une bonne précaution qui ne mange pas de pain concernant les media-queries est d’utiliser min-width et max-width pour ne pas avoir de chevauchement des intervalles de résolution :

  1. @media screen and (max-width:320px) {}
  2. @media screen and (min-width:321px) and (max-width:640px) {}
  3. @media screen and (min-width:641px) {}

Télécharger

Dans une première intention, j’avais réussi à obtenir un palliatif complet qui semblait fonctionner avec sur le Nexus S de Mobitest.
Puis 2 semaines plus tard, refaisant de nouveaux tests cela ne marchait plus, sans savoir si Mobitest avant changé ses configurations de test ou non.

En revenant à la bibliographie, on s’aperçoit que Tim Kadlec a eu le même genre de déboires : dans les conclusions de son test n° 5 il mentionne d’abord qu’Androïd 2 charge toutes les images, puis dans de nouveaux tests ce n’est plus le cas.
De fait le chargement de la page correspondant à son test N°5 montre le chargement de 2 images avec pourtant 2 media-queries bien exclusives.

Conclusion : sur certaines configuration Android 2.x, on ne peut pas s’en sortir proprement avec les media-queries car leur prise en charge semble douteuse. Et il ne semble pas plus fiable d’essayer de se fier à screen.width.

Une seule image pour Android 2.x

Du coup on va pallier ce dysfonctionnement de manière un peu plus radicale. Android 2 concerne essentiellement des smartphones. On va donc envoyer une unique image vers les utilisateurs d’Android 2.

Comme compromis, on choisit l’image correspondant à une largeur de 320px en 1.5x : cette image de 480px s’affichera bien sur les largeurs de 320px en résolution 1.5x tout en étant capable de s’afficher en 480px sans upscaling du navigateur.

Pour ce faire, on utilise JavaScript pour ajouter une classe android2 sur <html> :

  1. function hAC(c){(function(H){H.className=H.className+' '+c})(document.documentElement)}
  2. var android2 = (/android 2[.]/i.test(navigator.userAgent.toLowerCase()));
  3. if (android2) {hAC('android2');}

Télécharger

Puis on ajoute html:not(.android2) sur toutes les règles CSS de nos media-queries pour qu’elles ne correspondent à aucun nœud du DOM, ce qui évite Android 2 de charger les images (ouf au moins ça marche, ça !).

Et enfin on ajoute une règle CSS dédiée avec l’image unique :

  1. html.android2 span.c-1841222306,html.android2 span.c-1841222306:after{ }

Dans le cas où JavaScript ne serait pas actif, le rendu sera correct, mais il est possible, dans certains cas que l’on ne sait pas prédire, que le navigateur charge plusieurs versions de chaque image, ralentissant son chargement.

Détection de la qualité de connexion

À ce stade nous avons une solution qui prend en compte la taille du viewport et la résolution de l’écran. Il nous reste à pondérer cela par la qualité de la connexion. On veut par exemple éviter de charger une image 2x sur un téléphone en edge alors qu’une image 1x rendrait le meilleur service dans ce cas.

Lors de sa conférence Adaptive Images for Responsive Web Design Christopher Schmitt a eu cette image : mesurer la vitesse de la connexion c’est comme se mettre devant une voiture pour voir à quelle vitesse elle roule.
De fait, dans la solution HiSrc qu’il développe, il prend le parti de faire la mesure en téléchargeant une image test de 50ko et en mémorisant le résultat pendant 30 minutes dans le navigateur à l’aide de localStorage.
Cette méthode a, à mon sens, 2 inconvénients : c’est embêtant de télécharger 50ko pour rien si on a une connexion lente, et en 30 minutes on a eu le temps de se déplacer d’un wifi qui va vite au couloir du métro en Edge, en passant par la rue en 3G.

Dans notre cas, on embarque dans le HTML une image JPG en DATA URI, de basse qualité, en guise de prévisualisation. Du coup la page HTML elle-même, pour peu qu’elle contienne 2 ou 3 images, représente un échantillon test intéressant.
Pourquoi ne pas utiliser la toute nouvelle Navigation Timing API pour mesurer le temps de chargement de la page HTML et en déduire la qualité de la connexion ?

L’avantage de cette méthode est d’avoir une détection à chaque hit, donc la plus à jour possible.
L’inconvénient est que si elle assez bien supportée par tous les navigateurs récents, Safari ne la prend en charge dans aucune version (aussi bien dans sa version bureau que sur iOS). Mais on fait le pari que cela changera dans une prochaine version, et la fragmentation de version de périphériques iOS étant réduite cela permettra alors à tout le monde d’en bénéficier.

Pour les navigateurs Android plus anciens (2.2+ et 3.x) qui ne supportent pas cette API on utilise navigator.connection comme dans Modernizr.

Enfin la taille du document HTML est mesurée côté serveur, ce qui permet de faire la mesure de vitesse dès le début du <head> et de placer une classe aislow sur l’élément <html> si la connexion est lente :

  1. var slowConnection = false;
  2. if (typeof window.performance!=="undefined"){
  3. var perfData = window.performance.timing;
  4. // approx, *1000/1024 to be exact
  5. var speed = ~~(119321/(perfData.responseEnd - perfData.connectStart));
  6. // value of 50 to be confirmed : effective bandwidth including latency
  7. slowConnection = (speed && speed<50);
  8. } else {
  9. var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
  10. if (typeof connection!=="undefined") slowConnection = (connection.type == 3 || connection.type == 4 || /^[23]g$/.test(connection.type));
  11. }
  12. if(slowConnection) {hAC('aislow');hRC('aihrdpi');}

Télécharger

On utilise alors cette classe pour conditionner le sélecteur sur les media-queries qui cibles les résolutions 1.5x et 2x avec un html:not(.aislow).

En conséquence, si JavaScript n’est pas actif, ou si on ne sait pas détecter la qualité de la connexion comme sur iOS, on continuera à charger les images en résolution 1.5x ou 2x si l’écran le permet.

On pourrait prendre le parti inverse et ne charger les images haute résolution que si on sait que la connexion est rapide. Mais d’un autre côté on a un mécanisme de rendu progressif, grâce à la prévisualisation de basse qualité, donc cela parait acceptable de prendre le risque de charger une image un peu plus lourde.

Direction artistique

La question de la direction artistique apparaît quand on considère qu’il serait préférable que les images affichées sur petit écran soient recadrées autour du centre d’attention. En effet un simple redimensionnement peut rendre l’image originelle difficilement lisible car avec des détails trop petits.

JPEG - 69 ko
À gauche la version pour mobile, au centre une version pour, disons, "tablette". A droite la plus grande image.
Exemple tiré de http://css-tricks.com/which-responsive-images-solution-should-you-use/

La méthode des 3 couches permet tout à fait la prise en compte de la direction artistique, car elle permet de préciser des images différentes en fonction de la taille de l’écran.

Cependant, l’image de prévisualisation est unique, et ne dépend pas des périphériques. Il faut donc choisir si on construit la prévisualisation à partir de l’image pour mobile ou de l’image pour grand écran.
Dans la mesure où l’image de prévisualisation est de basse qualité, elle rend mal les petits détails, et il semble donc logique de partir de l’image pour petits écrans quand celle-ci est fournie.
D’autant plus que c’est sur les petits écrans, en mobilité, avec une mauvaise connexion, que l’image de prévisualisation apparaitra le plus longtemps et le plus souvent (plus d’attente pour charger l’image adaptée).

Il faut cependant apporter un bémol : comme on ne dispose que d’une unique image de prévisualisation, on va se retrouver dans certains cas (sur les grands écrans) à superposer une image adaptée assez différente de l’image de prévisualisation.

En cas de d’utilisation de la transparence dans l’image, cela peut générer un défaut de rendu assez gênant : notre méthode est probablement pas idéale dans ce cas, et empêchera parfois d’utiliser une variante pour mobile si le rendu sur grand écran est dégradé.

Mise en place et automatisation

Notre technique des 3 couches nécessite d’être mise en place côté serveur, pour injecter le balisage HTML ainsi que les media-queries. En contre-partie elle fonctionne sans nécessiter JavaScript. Lorsque JavaScript est actif elle permet en plus un rendu progressif, et la prise en compte de la qualité de la connexion.

La quantité de code à mettre en place pour chaque image est cependant relativement importante, en particulier en ce qui concerne les styles et media-queries, et il n’est pas question de faire cela manuellement.
De plus, la production des images de différentes tailles et résolution est fastidieuse et doit être automatisée.

C’est ce que fait la la librairie externe AdaptiveImages :

  • génération automatique de l’image de prévisualisation (lowsrc) ;
  • génération de toutes les variantes d’image pour les breakpoints pré-configurés, en ajustant la compression JPG à la résolution ;
  • remplacement du markup <img/> à la volée ;
  • regroupement de tous les styles CSS dans le <head> pour accélérer le rendu de la page [14] ;
  • injection du JavaScript de compatibilité navigateurs et de détection de qualité de connexion ;
  • prise en charge d’une version mobile si renseignée dans l’attribut data-src-mobile.

Cette librairie est utilisée par le plugin Adaptive Images pour SPIP, qui fournit en plus une interface d’ajout de la variante mobile dans l’interface de contribution, ainsi que le plugin Adaptive Images pour DotClear.

Benchmark&démo

Une page de démo permet de voir le plugin en fonction pour divers type d’image.

La performance Web de la page est comparée avec et sans fonctionnement du plugin. A noter qu’en l’absence du plugin les images sont toutes retaillées à 640px maximum de large.
Pour les tests de performance, j’ai enlevé l’image GIF animée car celle-ci n’est de toute façon pas prise en charge, et son poids seul déterminait le temps de chargement dans tous les cas.

Images normales Images adaptatives
IE 8 (Paris) 455ko - 3,1s 490ko - 3,3s
Firefox (Bruxelles) 451ko - (5.2s) [15] 485ko - 3,6s
Nexus S Androïd 2.3 (Cambridge) 450ko - 2,45s [16] 372ko - 3,3s [17]
iPhone 4 iOS 5 (Cambridge) 457ko - 3,0s 432ko - 3,2s

Nous avons 4 images adptatives sur cette page.
Sur les navigateurs Desktop :

  • le surpoids du HTML qui embarque la prévisualisation est de 35ko environ, soit environ 8% du total téléchargé initial ;
  • le temps nécessaire pour arriver à la page finie de charger est quasi identique entre les deux configurations (gain sur Firefox mais non significatif) ;
  • le début du chargement des images est légèrement retardé sous Firefox : c’est normal puisqu’on ne bénéficie plus du chargement anticipé des balise <img> et qu’il faut attendre le parsing CSS pour lancer le chargement ;

Sur le Nexus :

  • on gagne près de 18% du poids de chargement ;
  • le temps de chargement dans ces conditions est dégradé, mais le temps affiché par les tests semble dans tous les cas optimiste, et le rendu n’est pas encore fini.

Sur l’iPhone :

On s’aperçoit qu’avec une connexion de bonne qualité les temps de chargement de sont pas améliorés par l’utilisation d’images adaptées aux périphériques.

C’est un peu un paradoxe, car c’était pourtant un des objectifs recherchés !
Mais c’est pourtant une conséquence presque automatique des techniques de chargement d’image adaptées : le chargement ne peut effectivement démarrer que lorsque le navigateur dispose de suffisamment d’informations de rendu.
Et il n’est pas du tout certain que les navigateurs pourront faire mieux avec un markup natif dédié à cet usage.

Il faut modérer cette première impression par plusieurs facteurs :

  • plus la connexion sera de mauvaise qualité, plus l’adaptation des image permettra de gagner du temps de chargement par rapport à la page avec images de base ;
  • nos images desktop sont ici de tailles assez modestes par défaut (640px) ;
  • nous embarquons une prévisualisation qui nous donne un rendu progressif, ce qui en général améliore le ressenti de l’utilisateur ;
  • nous améliorons la qualité des images affichées sur les grands-écrans de haute résolution.

Globalement, la qualité du service rendu à l’utilisateur est donc plutôt améliorée.

Mais il est clair que l’utilisation d’Adaptive Images n’a de sens que si on veut servir à la fois de grandes images de haute résolution pour les tablettes et desktop, et ne pas desservir pour autant les smartphones à petits écrans (voire des montres ?).
Si les meilleurs images dont on dispose ont des dimensions de l’ordre de 640px, il n’est pas du tout sur que le jeu en vaille la chandelle, et il est probablement aussi bien de servir ces images pour tous les utilisateurs.

Résumé

La technique des 3 couches est une solution utilisable dès maintenant sur les sites responsives, car compatible avec le parc des navigateurs utilisés actuellement, et complètement automatisable.

Elle répond à presque tous les objectifs initiaux, mais présente tout de même quelques inconvénients :

  • même si on a réussi à avoir un seul hit par image, on charge au final 2 images différentes pour chaque : la prévisualisation embarquée dans le HTML et l’image adaptée. L’originalité de la méthode est d’exploiter la première pour en faire un équivalent fonctionnel du défunt lowsrc ;
  • le HTML de la page est plus lourd car il embarque les images de prévisualisation : cela peut retarder le moment du début du rendu dans certaines conditions ;
  • la méthode introduit un markup spécifique avec un wrapper autour de la balise <img/> dont il faut tenir compte éventuellement dans les CSS. Par exemple, si j’applique des coins ronds à la seule balise <img/> ceux-ci ne seront pas appliqués au wrapper et l’image affichée n’aura pas de coins ronds ;
  • l’adaptation de la taille de l’image se fait en fonction de la taille du viewport et non de la largeur dans laquelle l’image est affichée (ce qui était un gros intérêt de la méthode Clown Cars) ;
  • elle se prête difficilement à une mise en place manuelle sur un site statique.

On l’avait dit dès le début : aucune solution reposant sur les techniques disponibles n’est parfaite sourire.

Il me semble cependant que la technique des 3 couches présente un rapport performance/coût intéressant pour les sites dynamiques en général, et pour les sites construits avec SPIP en particulier.

A condition de disposer de grandes images de qualité que l’on veut servir aux utilisateurs de grands écrans 2x !

Notes

[1on parle de device en anglais

[3en général et dans SPIP en particulier

[*ou autre

[4Marie a cherché pourquoi, il ne faut pas…

[5Je me suis retrouvé face à Estelle un matin de Paris-Web, et ce n’est que le lendemain que j’ai réalisé que les 2 Estelle n’étaient qu’une, à mon grand désespoir

[6bug constaté par Estelle sous WebKit mais censé être corrigé par l’implémentation dans la balise <object>

[7Pour des questions de performance, cette image sera toujours au format JPG, même si l’originale est en PNG ou GIF, quitte à perdre la transparence sur cette image de prévisualisation

[8l’étiquette n’est pas forcément unique dans la page, car elle est associée à l’image qui peut y apparaître plusieurs fois

[9Les esprits chagrins rappelleront que ce n’est pas valide et ralentit le rendu de la page, et ils ont raison. On traitera ce point plus tard

[11il faut nettoyer le résultat qui est une directive CSS de la forme url("...")

[12en fait on pourrait améliorer en ajoutant le support pour IE9 desktop qui supporte tout ce qu’il faut. Mais cela complique bien inutilement les commentaires conditionnels alors que les utilisateurs n’ont en général ni petit écran, ni d’écran haute résolution

[13avec à la clé un volume d’images chargées beaucoup plus élevé que sur la version desktop initiale

[14et consoler les esprits chagrins qui auront suivis jusque là, les courageux

[15temps total bizarre, reproductible, mais injustifié, a priori non sifgnificatif

[161.8s pour avoir les images de contenu

[172.35s pour avoir les images de contenu

À propos de cet article

Répondre à cet article

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici
  • Ce formulaire accepte les raccourcis SPIP [->url] {{gras}} {italique} <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Suivre les commentaires : RSS 2.0 | Atom