RWD et SPIP : transmettre des images à la bonne dimension

En application des principes du Responsive Web Design, on utilise jQuery Picture pour faire charger par le navigateur une image adaptée à la largeur de la fenêtre (viewport), ceci afin de réduire les temps et les volumes de chargement. C’est intéressant lorsque les conditions de réception sont mauvaises et aussi pour réduire la facture.

Le plugin jQuery Picture pour SPIP développé par DnC met en œuvre le script jQuery Picture créé par Abban Dunne.

Voir : jQuery Picture A jQuery plugin to help ease the transition to responsive images

Problème à résoudre

Dans la démarche de conception réactive (Responsive Web Design ou RWD), les blocs de contenu sont redimensionnés pour s’adapter à la largeur d’écran tandis que le zoom est généralement fixé à 1 afin de présenter les textes à une taille normale. Se pose alors la question de la dimension des images.

Les principes du RWD conduisent à réaliser un rendu fluide, dans lequel les largeurs des colonnes et des images seront automatiquement ajustées en fonction de la largeur de l’écran. Les images transmises à une dimension adaptée à la largeur du site web "normal" sont trop grandes et le navigateur cible doit les redimensionner.

L’enjeu consiste à transmettre une image de largeur la plus réduite possible pour réduire le volume de transmission inutile.

Pour résoudre le problème, il faut considérer plusieurs situations :

1 - L’image est une icône, un label ou une imagette suffisamment petite pour conserver sa dimension d’origine. C’est généralement le cas pour les images de largeur inférieure à 150px ou 100px.

2 - L’image est présentée "en pleine largeur". Il s’agit par exemple d’une image occupant 100% de la largeur de la colonne du contenu principal.

3 - L’image a une largeur réduite à une fraction de la largeur de l’écran. Il s’agit par exemple d’une image occupant 1/3 de la largeur de la colonne du contenu principal flottante à gauche ou à droite d’un texte.

Dans le premier cas, on conserve généralement la largeur de l’image.

Dans le deuxième et le troisième cas, il faut charger une image dont la largeur est choisie en fonction de celle de l’écran.

Principe de fonctionnement

Le principe général consiste à créer pour chaque image, côté serveur, un jeu de différentes dimensions ou "vignettes". Côté client, le script jQuery Picture est utilisé pour réécrire l’URL de l’image de façon à sélectionner la bonne vignette compte tenu de la largeur du bloc conteneur.

Comme on ne peut créer à l’avance des images de toutes les largeurs possibles, on divise l’espace des largeurs d’écran en un nombre réduit d’intervalles (par exemple 4), dont les valeurs limites sont appelées "noeuds".

Dans un environnement SPIP, le plugin fournit les modèles doc, images et img mettant en œuvre le script jQuery Picture. Il utilise le filtre image_reduire pour créer en cache IMG les différentes vignettes de l’image.

Le plugin adopte les convention de nommage des blocs de Zpip : le conteneur dont on mesure la largeur est le bloc #contenu.

Réalisation du plugin jQueryPicture

Insertion de code dans le header

Le plugin jQueryPicture met en œuvre la fonction de pipeline insert_head pour effectuer deux actions :
- insérer un appel à jquery-picture.js
- appliquer la méthode picture() aux éléments de classe .figure.responsive.

function jqpic_insert_head($flux){
       $flux .= '<script async type="text/javascript" src="'._DIR_PLUGIN_JQPIC.'javascript/jquery-picture.js"></script>';

       $flux .= '<script async type="text/javascript">      
       $(function(){
       $("figure.responsive").picture();
       });
       </script>';
       return $flux;
   }

Préparation des images côté serveur

Ceci se fait en perfectionnant grâce à SPIP la méthode proposée par Abban Dunne. Plutôt que d’avoir à créer manuellement un jeu d’images de dimensions différentes, le filtre image_reduire est utilisé pour préparer automatiquement les différentes dimensions de l’image :

<figure class="responsive"
data-media="[(#URL_DOCUMENT|image_reduire{#GET{scale0,#EVAL{240-#ENV{marges,0}}},0}|extraire_balise{img}|extraire_attribut{src})]"
data-media240="[(#URL_DOCUMENT|image_reduire{#GET{scaleA,#EVAL{320-#ENV{marges,0}}},0}|extraire_balise{img}|extraire_attribut{src})]"
data-media320="[(#URL_DOCUMENT|image_reduire{#GET{scaleB,#EVAL{480-#ENV{marges,0}}},0}|extraire_balise{img}|extraire_attribut{src})]"
data-media480="[(#URL_DOCUMENT|image_reduire{#GET{scaleC,#EVAL{768-#ENV{marges,0}}},0}|extraire_balise{img}|extraire_attribut{src})]"
title="#TITRE"
>
[<a href="(#ENV{lien})"[ class="(#ENV{lien_class})"] title='#TYPE_DOCUMENT -         [(#TAILLE|taille_en_octets|texte_backend)]'[ type="(#ENV{lien}|?        {#ENV{lien_mime},#MIME_TYPE})"]>]
        [(#ENV{lien}|?{</a>})]
</figure>

Mécanisme de base : "image en pleine largeur"

Le serveur prépare des images aux largeurs de 768px, 480px, 320px, et 240px. Pour un bloc #contenu de largeur inférieure à 240px, le modèle appelle l’image de largeur 240px ; pour un bloc #contenu compris entre 321 et 480, il appellera celle de largeur 480px etc.

Image réduite à une fraction de la largeur du bloc #contenu : paramètre percent

Lorsque l’image ne doit occuper qu’une partie de la largeur du bloc #contenu, il devient nécessaire de réduire en proportion la vignette de façon à éviter de consommer inutilement de la bande passante avec des images trop larges. Pour cela, un paramètre percent fixe le rapport de réduction de l’image.

Pour les images flottantes, et afin d’éviter que le texte se trouve coincé dans une colonne trop étroite entre l’image et la bordure, le paramètre percent est fixé par défaut à 25% lorsque les paramètres left ou right sont fixés. Si l’image n’est pas flottante, ou si le paramètre de positionnement est center, la valeur de 100% est appliquée en l’absence de paramètre percent.

Donc, comme le paramètre percent n’est pas indispensable, il devient possible d’appliquer le plugin à un site existant et d’obtenir des résultats convenables.

Notons que les images ne sont jamais agrandies.

Utilisation

Exemples d’affichage d’une image dans un article de SPIP

Le plugin jQueryPicture surcharge les modèles doc, image et img pour intégrer le mécanisme.

Affichage d’une image simple, sans titre ni texte :

<img75|left|percent=33>
<img75|left>  équivaut à : <img75|left|percent=25>

Affichage d’un document avec titre et texte éventuels :

<doc75|left> qui équivaut à : <doc75|left|percent=25>

etc. avec les modèles emb et image ou tout modèle appelant doc, image et img.

Exemples d’affichage dans un squelette

Dans toute boucle où la balise #URL_DOCUMENT est définie, l’exemple suivant met en œuvre le modèle img :

#MODELE{img, percent=50}  ou #MODELE{img}{percent=50}

L’intérêt du modèle emb est qu’il tient compte du type mime du document défini par #URL_DOCUMENT pour appeler le bon modèle. Les expressions suivantes mettent en œuvre le modèle image à travers le modèle emb lorsque le fichier est du type image/... :

#MODELE{emb, percent=50}  ou #MODELE{emb}{percent=50}  
#MODELE{emb}  qui équivaut à : #MODELE{emb}{percent=100}
[(#MODELE{emb, lien=#URL_SITE_SPIP/?page=photo&id_document=#ID_DOCUMENT}|image_reduire{640,0})]

Le dernier appel, sans paramètre percent, est équivalent à percent=100 parce qu’il n’y a pas de left ni de right. Un lien vers une page de photo est attaché à l’image.

[(#MODELE{emb}|image_reduire{640,0})]

Cet appel sans paramètre percent, a la particularité de limiter la largeur de la plus grande vignette à 640px au lieu de 748px.

Noter que dans :

[(#MODELE{emb, percent=50}|image_reduire{640})]

image_reduire ne servirait à rien.

Traiter la différence entre la largeur mesurée par jQuery Picture et la propriété css width : paramètre marges

Position du problème

La largeur que mesure jQuery Picture est obtenue avec la fonction jQuery .width sur le container #contenu. Cette fonction retourne les dimensions intérieures de la boîte, sans les éventuels margin, border et padding.

Pour avoir la largeur exacte en dips, il faudrait ajouter à la valeur mesurée les margin, border et padding des blocs #page, #conteneur et #contenu :

Pour une charte graphique épurée, une bonne pratique consiste à fixer une petite valeur de padding au bloc #page, par exemple 3 px. Dans ce cas, et quand la présentation est en colonne unique, la largeur mesurée par le plugin est inférieure de 6 dips à la largeur réelle du viewport.

Cela n’aura pas une incidence négative sur la sélection de la dimension de l’image. Cela va plutôt dans le sens de l’économie de bande passante : un viewport de 246 dips (cela existe-t-il ?) sera vu comme faisant 240 dips et recevra une image de 320px au lieu de 480px. En revanche, cela revient (par exemple pour le noeud 240) à afficher une image de 240px dans une boîte faisant au plus 234px, alors que l’on pourrait se contenter d’envoyer une image à la dimension exacte.

De même la valeur du paramètre percent (qui s’applique à la largeur du noeud) n’a pas exactement la même signification que le paramètre css width exprimé en % des éléments insérés dans #contenu.

Pour résoudre ces deux problèmes, afin qu’il y ait correspondance exacte (au moins lorsque le viewport fait la largeur du noeud), il faut :
- soit que toutes les valeurs de margin, border et padding des blocs #page, #container et #contenu soient nulles ;
- soit modifier les valeurs de référence du filtre image_reduire en les diminuant de la somme des valeurs droites et gauche des margin, border et padding des blocs #page, #container et #contenu. La solution la plus simple est d’ajouter un paramètre marges fournissant aux modèles cette largeur en pixel. Cependant, cela n’est rigoureux qu’à la condition que les valeurs ne changent pas en fonction des @media.

Exemple avec le paramètre marges

Exemple tiré de theme_Z_photo de photo.degoy.com dans lequel seul le bloc #page a un padding de 3px à gauche et à droite :


#MODELE{emb, percent=100, marges=6}

Vérification avec un viewport de 762 dips (sur desktop, régler la largeur du navigateur à 784px à l’aide de whatsmy.browsersize.com pour tenir compte des 16px de largeur de l’ascenceur) : on obtient bien une image de 762px non redimensionnée par le navigateur.

Avec l’URL :

http://photo.degoy.com/Metiers?id_d...

on vérifie que PageSpeed ne signale pas d’image redimensionnée.

Bonnes pratiques pour éviter le redimensionnement des images par le navigateur

- ne jamais fixer la largeur des images avec la propriété css width, mais toujours la fixer avec le paramètre percent ;
- toujours préciser le paramètre marges lorsque l’image est susceptible d’occuper toute la largeur (ou presque) du bloc #contenu, en particulier lorsque le paramètre percent est implicitement ou explicitement fixé à 100%.

Considération pour la conception des blocs principaux

Afin d’être cohérent avec les valeurs de nœuds considérées :

1. Au-dessus de 480px de largeur de viewport, l’image est limitée à 768px, comme si le nœud suivant avait pour valeur 768px (la petite dimension d’un iPad 2 ou 3). Une bonne idée serait de réduire la plus grande dimension des images à une largeur de 768px au maximum avant de les charger dans SPIP.

2. On concevra le site pour desktop avec une largeur maximale de 1024px. Ce sera la seule largeur fixée en pixels du CSS. Une colonne de contenu principal #contenu ayant une largeur fixée à 75% soit 768px, et une colonne latérale (bloc #navigation) fixée à auto soit 256px.

3. Généralement, la présentation pour mobile ne conservera que la colonne principale (bloc #contenu). Le monde se divise en deux : ceux qui sont trop petits pour présenter autre chose que la colonne principale (les mobiles), et les autres (les desktops).

Deux options semblent intéressantes :

A - La valeur de max-width pourrait être fixée de façon à ce que un iPad (viewport de 768 x 1024px) ou une tablette Android (viewport de 800 x 1200px par exemple) fassent apparaître la colonne droite en mode paysage, mais la masquent en mode portrait. Une valeur de 980px parait appropriée .

Dans le cas d’un iPad : en mode vertical (largeur 768px), il ne présenterait que la colonne de contenu principal. En mode horizontal (largeur 1024px), il pourrait présenter tout. Cependant, Safari pour mobile retourne device-width = 768px dans les deux cas ?

B - Permettre à toutes les tablettes (et aux plus grands smartphones) d’afficher la colonne de droite. Une valeur de 640px parait appropriée dans ce cas .

Nota 1 : Compatibilité de jQueryPicture avec jQuery Lazy Load

Il faut que Picture ait traité les balises img avant Lazy Load.

Il faut obtenir :

<script type="text/javascript">
       $(function(){
       $("figure.responsive").picture();
       });
</script>
<script type="text/javascript">
       $(function(){
           $("#contenu img").lazyload({
               threshold : 0,
               placeholder : "plugins/auto/jquery_lazyload/images/grey.gif",
           });
       });
</script>

et non l’inverse.

Pour cela, il faut contrôler l’ordre dans lequel s’installent les plugins. Je n’ai trouvé que la solution d’indiquer dans le fichier plugin.xml de Lazy Load :

<necessite id="jqpic" version="[0.1;]">

Ce n’est pas bien de modifier un plugin !

Autre solution : intégrer Lazy Load dans Picture ???

Nota 2 : dans le header

Il ne faut pas oublier de mentionner dans le header :

<meta name="viewport" content="width=device-width">  (1)
<meta name="viewport" content="initial-scale=1.0">

Avec Zpip, cela est fait généralement en tête du fichier structure.html du thème.

(1) ou peut-être seulement la 1° instruction ?

Une idée, un projet?

Degoy net Consultants
76 avenue du Général Leclerc
92340 - Bourg la Reine

SARL au capital de 7500 €