Slider d’images : attention au temps de chargement Optimisation et Webperformance sous SPIP

, par Cédric

Les sliders (ou carrousels) constituent un élément d’interface assez criticable, et plusieurs études tendent à montrer qu’ils sont mentalement filtrés par les utilisateurs au même titre que la publicité.

Cependant, un usage défendable est le diaporama d’image, qui à un but illustratif plutôt que de mise en avant du contenu. Mais lorsqu’on utilise un slider dans ce but, il faut faire attention au temps de chargement de la page qui peut vite se dégrader !

Dans notre cas, un client utilise le plugin SPIP Nivoslider pour afficher sur la home page un diaporama de plusieurs images modifiables par l’utilisateur (qui peut donc en mettre beaucoup !) en assez grand format.

Malheureusement il en résulte un temps de chargement de la page assez important : plus de 6s mesurés par WebPageTest sous IE8 avec une connexion de type ADSL, pour 8 images affichées dans le diaporama.

Nous allons voir comment une optimisation assez simple à mettre en œuvre permet de gagner environ 2 secondes sur le temps de chargement et sur le temps d’affichage de la page.

Un bouchon dans le chargement de la page

Il suffit de regarder la cascade de chargement de la page pour comprendre le problème :

PNG - 52.3 ko
Cascade de chargement de la page avant optimisation

Toutes les images du diaporama sont chargées d’un coup, au début du chargement de la page (presque en premier après le chargement des fichiers CSS et JS, dont on remarque qu’ils sont bien concaténés et minifiés).
Le chargement des images consomme toute la bande passante et pendant ce temps là aucune autre image de la page ne peut se charger.

Regardons comment le diaporama est construit. Le squelette chargé de son affichage est tout simple : il affiche les images associées à un article dans un conteneur html, et appelle le javascript de Nivoslider pour lancer le diaporama ensuite.

Le squelette inclus inclure/slider.html utilisé pour afficher le diaporama est assez simple :

<!-- SLIDER -->
<div id="cadre-slider">
 <div class="slider-wrapper theme-default">
   <div id="slider" class="nivoSlider">
     <BOUCLE_doc(DOCUMENTS){id_article}>
     [(#URL_DOCUMENT
|image_aplatir{jpg,ffffff,75}
|inserer_attribut{alt,#TITRE})]
     </BOUCLE_doc>
   </div>
 </div>
</div>

<script type="text/javascript">
jQuery(window).load(function() {
 jQuery('#slider').nivoSlider({
   effect: 'fade', // Specify sets like: 'fold,fade,sliceDown'
   animSpeed: 1000, // Slide transition speed
   pauseTime: 3000 // How long each slide will show
 });
});
</script>

Seule la première image est visible initialement, les autres étant masquées par la feuille de style du site. Alors pourquoi sont-elles toutes chargées d’un coup ?

Comment le navigateur décide de charger (ou non) les images ?

Ce comportement est tout à fait normal : les navigateurs modernes sont optimisés pour charger le plus vite possible toutes les images de la page. Pour cela le navigateur repère toutes les balises <img> dans le HTML de la page et lance leur chargement dans leur ordre d’apparition.
Le navigateur n’attend pas de faire le rendu CSS pour lancer ce chargement, car cela lui ferait perdre trop de temps. D’ailleurs certains navigateurs n’attendent même pas d’avoir construit le DOM : ils repèrent et lancent le chargement des images le plus vite possible, en consultant le HTML brut de la page.

Dans tous les cas, le navigateur ne sait pas quelles images seront visibles ou non quand il décide de lancer leur chargement.

La stratégie choisie est donc de considérer que toutes les images présentes dans la page sont utiles et devront être chargées : autant commencer le plus vite possible !

Mais dans notre cas c’est bien dommage, car seule la première image est visible initialement, les autres ne l’étant que plus tard, au fur et à mesure de l’animation du diaporama.

Optimiser le diaporama

Pour optimiser le temps de chargement de la page, nous allons donc aider un peu le navigateur en modifiant la construction de notre diaporama.

Modifier le HTML

Puisque le navigateur charge toutes les images qu’il trouve dans la page, nous allons modifier le code HTML pour n’y indiquer que la première image, qui doit être visible lors de l’affichage initial, mais pas les suivantes. De cette façon, même si un utilisateur ne dispose pas de javascript activé, il pourra voir la première image s’afficher, même si il ne voit pas le diaporama : on ne dégrade pas le service rendu.

Pour cela nous créons un filtre SPIP dans le fichier PHP inclure/slider_fonctions.php. Ce petit filtre aura pour but de laisser la première image visible, et de renommer l’attribut src en data-src sur les images suivantes.

On lui passe donc la balise <img> en premier argument, puis le compteur de la boucle en second argument :

<?php
function slider_img_masque_src($img, $compteur){
       if ($compteur==1) return $img;
       $src = extraire_attribut($img,"src");

       $img = inserer_attribut($img,"data-src",$src);
       $img = vider_attribut($img,"src");
       return $img;
}

Dans le squelette inclure/slider.html on modifie la boucle en appliquant le filtre sur la balise qui affiche l’image :

     <BOUCLE_doc(DOCUMENTS){id_article}>
     [(#URL_DOCUMENT
|image_aplatir{jpg,ffffff,75}
|inserer_attribut{alt,#TITRE}
|slider_img_masque_src{#COMPTEUR_BOUCLE})]
     </BOUCLE_doc>

Ainsi, là ou on avait auparavant le HTML suivant :

<img src='local/cache-gd2/8ba0c55328110304d5e57c7903f19faa.jpg' width='950' height='271' alt='' />
<img src='local/cache-gd2/ef82a3256f30c66f2399d7aadf67b34d.jpg' width='950' height='180' alt='' />
<img src='local/cache-gd2/8626fcf4d611aa86a1e46b70e00bc932.jpg' width='950' height='351' alt='' />
<img src='local/cache-gd2/5c6c47afa6b54b57d92c11861b6fcd14.jpg' width='950' height='180' alt='' />
<img src='local/cache-gd2/d589cf24c0ff701625eb9d2afb5f6884.jpg' width='900' height='200' alt='' />
<img src='local/cache-gd2/3a5849e9722782647f1c59f590a8fd7d.jpg' width='950' height='235' alt='' />
<img src='local/cache-gd2/1a6ee7fc86d556788621ed6135fcf7e0.jpg' width='950' height='197' alt='' />      
<img src='local/cache-gd2/177305720686894776afbfedd61ff70e.jpg' width='946' height='180' alt='' />

on a maintenant le source suivant, où seule la première image est affichable par le navigateur :

<img src='local/cache-gd2/8ba0c55328110304d5e57c7903f19faa.jpg' width='950' height='271' alt='' />
<img width='950' height='180' alt='' data-src='local/cache-gd2/ef82a3256f30c66f2399d7aadf67b34d.jpg' />
<img width='950' height='351' alt='' data-src='local/cache-gd2/8626fcf4d611aa86a1e46b70e00bc932.jpg' />
<img width='950' height='180' alt='' data-src='local/cache-gd2/5c6c47afa6b54b57d92c11861b6fcd14.jpg' />
<img width='900' height='200' alt='' data-src='local/cache-gd2/d589cf24c0ff701625eb9d2afb5f6884.jpg' />
<img width='950' height='235' alt='' data-src='local/cache-gd2/3a5849e9722782647f1c59f590a8fd7d.jpg' />
<img width='950' height='197' alt='' data-src='local/cache-gd2/1a6ee7fc86d556788621ed6135fcf7e0.jpg' />      
<img width='946' height='180' alt='' data-src='local/cache-gd2/177305720686894776afbfedd61ff70e.jpg' />

Modifier le JS

Il suffit ensuite de modifier le javascript pour charger les images au fur et à mesure. Là, on a du faire quelques essais pour trouver le bon compromis : il ne faut pas attendre l’affichage d’une image par le slider pour lancer son chargement, car dans ce cas elle n’est pas prête à temps et ne s’affiche pas, laissant un vide.
La bonne solution est donc de lancer le chargement de l’image suivant quand on vient juste d’afficher une image. Et par suite, il faut prendre de l’avance, et charger la seconde image dès le lancement du slider (puisque la première image est déjà affichée).

Pour faire cela on créé une fonction javascript qui lance le chargement d’une seule image :

function sliderLoadNextImg(slider){
       var toload = jQuery("img:not(.loaded)[data-src^=]",slider);
       if (toload.length) { toload = toload.eq(0); toload.attr('src',toload.attr('data-src')).attr('data-src','').addClass('loaded');}
}

La fonction cherche la première image qui a un attribut data-src et le copie dans l’attribut src, ce qui lance son chargement. Si toutes les images sont déjà chargées, la fonction ne fait rien.

Il suffit alors d’utiliser les callback proposées par nivoslider pour appeler cette fonction :

  • lors de l’initilisation du slider
  • après l’affichage d’une image

Ce qui nous donne :

<script type="text/javascript">
function sliderLoadNextImg(slider){
       var toload = jQuery("img:not(.loaded)[data-src^=]",slider);
       if (toload.length) { toload = toload.eq(0); toload.attr('src',toload.attr('data-src')).attr('data-src','').addClass('loaded');}
}
jQuery(window).load(function() {
       jQuery('#slider').nivoSlider({
               effect: 'fade', // Specify sets like: 'fold,fade,sliceDown'
               animSpeed: 1000, // Slide transition speed
               pauseTime: 3000, // How long each slide will show
               afterLoad: function(){sliderLoadNextImg(jQuery('#slider'))},
               afterChange: function(){sliderLoadNextImg(jQuery('#slider'))}
       });
});
</script>

Un chargement plus rapide

On évalue avec l’outil WebPageTest l’impact de notre optimisation sur le chargement de la page :

PNG - 42.3 ko
Cascade du chargement après optimisation

On voit clairement que le bouchon a sauté : une seule image chargée qui laisse la place au chargement des autres composants de la page. Celle ci-se charge maintenant en un peu moins de 4.5s.

Un rendu plus rapide

A noter que les cascades montrent de manière tompeuse que le début du rendu aurait été retardé par l’optimisation.

L’analysé détaillée du film de rendu de la page montre que dans le premier cas le rendu ne débutait réellement qu’au bout de 5s (seule la couleur de background était rendue à 1.6s), et que la page n’était visuellement complète qu’au bout de 6.6s.

Après optimisation, le rendu débute vers 3s et est visuellement complet au bout de 4.3s (à ce moment la page était encore vierge avant l’optimisation).

Généraliser l’optimisation pour tous les utilisateurs

On voit qu’il est ainsi facile, moyennant quelques modifications, d’optimiser le chargement d’un diaporama à l’aide du plugin SPIP Nivoslider. Mais il est dommage de devoir faire soi-même ce travail à chaque utilisation du plugin.

Nous verrons donc dans un prochain billet comment améliorer le plugin pour qu’il propose par défaut la meilleure stratégie de chargement possible et généraliser ainsi les bonnes pratiques de Performance Web.

Crédit photo Mark Robinson