Niveau : Confirmé

Une galerie d'images simple avec jQuery

Ce tutoriel vous guide dans la réalisation d’une galerie d’images simple, avec JavaScript et la bibliothèque jQuery.

Tutoriel par (Interaction Designer, La Bruyère)
Créé le , mis à jour le (251752 lectures)
Tags : javascript, galerie, jquery, image, photo

Introduction

Logo jQuery

Réaliser une galerie photo en JavaScript n'est à première vue pas bien compliqué. Pourtant, si l'on veut bien faire les choses, le code peut rapidement devenir complexe et verbeux.

Voici un système simple et non intrusif de visualisation d'images avec chargement et effet de fondu, comme on peut le visualiser avec cet exemple. Grâce à jQuery, l'écriture de ce script s'en trouve grandement simplifiée et concise.

Attention: l’adjectif «simple» dans le titre du tutoriel désigne la galerie d’images réalisée, pas forcément les étapes pour y parvenir. Ce tutoriel est recommandé pour les personnes ayant déjà des connaissances de base en JavaScript, et connaissant si possible la librairie jQuery.

La base HTML

Nous allons commencer par créer une liste de vignettes qui, une fois cliquées, afficheront nos images grand format. Nous pourrions tout aussi bien écrire de simples liens textuels, cela n'a aucune importance sur le script que nous allons réaliser. Partons donc sur base d'une liste non ordonnée que nous appellerons thumbs et qui englobera nos vignettes photos.
Les images sont ici classées dans des dossiers big et small, eux-mêmes contenus dans un dossier images ; libre à vous de nommer différemment vos dossiers, voire de ne pas créer de dossier du tout.

<ul id="thumbs">
  <li>
    <a href="images/big/bernache.jpg">
      <img alt="Photo grand format d'une famille de Bernaches du Canada"
        src="images/small/bernache.png" />
    </a>
  </li>
  <li>
    <a href="images/big/bois.jpg">
      <img alt="Photo grand format d'un sous bois"
        src="images/small/bois.png" />
    </a>
  </li>
  <li>
    <a href="images/big/fleur.jpg">
      <img alt="Photo grand format d'un cœur de fleur"
        src="images/small/fleur.png" />
    </a>
  </li>
  <li>
    <a href="images/big/gouttelettes.jpg">
      <img alt="Photo grand format de gouttelettes sur une toile d'araignée"
        src="images/small/gouttelettes.png" />
    </a>
  </li>
  <li>
    <a href="images/big/rat.jpg">
      <img alt="Photo grand format d'un rat des prés"
        src="images/small/rat.png" />
    </a>
  </li>
</ul>

Nous avons maintenant notre balisage HTML définitif. Bien que laborieuse à consulter, notre galerie est fonctionnelle en l'état. Nous allons y rajouter une série d'interactions permettant d'améliorer l'expérience utilisateur dans le cas où la configuration de l'agent utilisateur le permet.

La surcouche JavaScript

Pour tirer parti des bénéfices qu'apporte jQuery, nous devons au préalable charger la bibliothèque depuis notre document HTML. Pour ce faire, nous allons utiliser le CDN de Google AJAX Libraries API qui apporte son lot de commodités, mais vous pouvez naturellement héberger la bibliothèque directement sur votre serveur si vous préférez. Importons donc la bibliothèque en écrivant dans la section <head> de notre document HTML:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>

Il reste à créer et lier le fichier qui contiendra le code de création de notre galerie d'images. J'ai choisi de l'appeler gallery.js mais à nouveau, vous pourriez le nommer tout autrement. Il est à noter qu'il est important de charger ce fichier après l'import de jQuery puisqu'il utilisera ce dernier pour exécuter bon nombre de méthodes.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
<script src="gallery.js" type="text/javascript"></script>

Nous pouvons maintenant commencer à écrire le code JavaScript dans notre fichier gallery.js. La majorité des scripts non intrusifs doivent logiquement s'assurer que le DOM soit chargé avant de pouvoir le manipuler. jQuery fait ça très bien en mettant à notre disposition l'événement ready(), remplaçant véloce du traditionnel window.onload. De manière générale, les scripts jQuery seront donc englobés dans la fonction suivante:

jQuery(document).ready(function(){
  // Mon script
});

La fonction jQuery() étant invoquée fréquemment, un alias a été établi permettant de remplacer son appel par $(), plus pratique et concis. Nous pourrions donc maintenant écrire :

$(document).ready(function(){
  // Mon script
});

La fonction jQuery() (ou $()) possède cette particularité bien commode d'être d'usage différent selon le contexte. Elle permet notamment par son simple appel de simuler de façon raccourcie le $(document).ready() vu ci-dessus. L'alias $ étant utilisé par de nombreuses bibliothèques, nous allons le passer en paramètre de la fonction jQuery (nommée de façon explicite) afin de protéger notre code d'éventuels conflits et de le rendre par la même occasion plus robuste et portable :

jQuery(function($){
  // Mon script
});

La première étape de notre script va être de générer une boîte HTML (un paragraphe en l'occurrence) qui fera office de récepteur pour les images grand format. Ce conteneur qu'on nommera viewer va être généré directement après notre liste #thumbs :

jQuery(function($){

  $("#thumbs").after(
    $(document.createElement("p"))
      .attr("id","viewer")
  );

});

Voyons étape par étape la construction de ce code:

  1. je récupère l'élément ayant pour identifiant thumbs grâce au sélecteur $("#thumbs")
  2. je lui applique la méthode after() qui permet de générer un contenu après ce dernier
  3. ce contenu généré peut être divisé en 3 étapes:
    1. je crée l'élément "p" en utilisant les méthodes classiques du DOM: document.createElement("p")
    2. j'englobe ce nouvel élément dans une fonction jQuery ($()) afin de le transformer en objet jQuery
    3. j'applique à ce nouveau paragraphe créé un identifiant viewer grâce à la méthode attr()

Afin de ne pas mélanger le code de construction de la galerie et les paramètres personnels, nous allons définir un objet settings au début de notre code contenant:

  • thumbListId qui représentera l'identifiant donné à notre liste de vignettes dans le code HTML
  • imgViewerId qui contiendra l'identifiant appliqué au paragraphe nouvellement créé

Le sélecteur d'identifiant $("#thumbs") ainsi que l'application de l'identifiant viewer sur le paragraphe conteneur peuvent dès lors faire appel à ces nouvelles variables. Nous allons au passage créer une variable imgViewer référenciant ce paragraphe afin d'y faire appel plus aisément par la suite:

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer"
  };

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
  );

  var imgViewer = $("#"+settings.imgViewerId);

});

Notre paragraphe est bien créé mais à ce stade vide de tout contenu. Vous pouvez vous aider d'outils tels que Firebug pour observer le code source généré.
Par défaut, nous afficherons dans ce paragraphe la première image de la liste. Nous allons logiquement devoir commencer par trouver l'URL de cette image. Pour ce faire, il nous faudra référencer toutes les vignettes cliquables dans un tableau (nommé thumbLinks) et en extraire le premier élément (firstThumbLink), situé à l'indice 0.

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0);

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
  );

  var imgViewer = $("#"+settings.imgViewerId);

});

Nous sommes maintenant en mesure d'afficher la première image grand format dans notre paragraphe récepteur. Il nous suffit d'extraire le contenu de l'attribut href de la première vignette photo et de le renseigner en tant que valeur de l'attribut src d'un nouvel objet image créé. Nous allons également en profiter pour faire de cette image grand format une variable bigPic qui nous servira plus tard.

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0);

  $("#"+settings.thumbListId).after($(document.createElement("p"))
    .attr("id",settings.imgViewerId)
    .append(
      $(document.createElement("img")).attr({
        alt: "",
        src: firstThumbLink.attr("href")
      })
    )
  );

  var imgViewer = $("#"+settings.imgViewerId),
  bigPic = imgViewer.children("img");

});

Notre première image apparaît désormais dans #viewer par défaut. Il nous faut maintenant nous attaquer à la partie centrale du script: la navigation entre les différentes images.

L'événement click() permet de définir une fonction qui sera déclenchée au clic sur chaque élément ciblé. Nous allons appliquer cette méthode pour tous les liens contenus dans #thumbs, maintenant définis par la propriété thumbLinks:

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0);

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
      .append(
        $(document.createElement("img")).attr({
          alt: "",
          src: firstThumbLink.attr("href")
        })
      )
  );

  var imgViewer = $("#"+settings.imgViewerId),
    bigPic = imgViewer.children("img");

  thumbLinks
    .click(function(){
      // Actions à déclencher
  });

});

La première étape consiste à annuler l'événement par défaut lors du clic sur un lien (représenté par le paramètre e) afin que le navigateur ne suive pas la cible du lien:

jQuery(function($){

  var settings = {
  thumbListId: "thumbs",
  imgViewerId: "viewer"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0);

  $("#"+settings.thumbListId).after(
  $(document.createElement("p"))
    .attr("id",settings.imgViewerId)
    .append(
    $(document.createElement("img")).attr({
      alt: "",
      src: firstThumbLink.attr("href")
    })
    )
  );

  var imgViewer = $("#"+settings.imgViewerId),
    bigPic = imgViewer.children("img");

  thumbLinks
  .click(function(e){
    e.preventDefault();
  });

});

L'action par défaut étant à présent annulée, nous pouvons réécrire le comportement à adopter lors du clic sur les vignettes. Quel est donc ce comportement? Il s'agit simplement de changer dynamiquement l'attribut src de bigPic en fonction de la vignette cliquée (grâce à l'attribut href). La vignette cliquée sera référencée par l'objet $(this) lui-même stocké dans une variable $this pour s'abstenir de parcourir plusieurs fois le DOM inutilement. Nous testerons également si l'image cliquée n'est pas d’ores et déjà l'image affichée afin d'éviter un rechargement inutile le cas échéant.

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0);

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
      .append(
        $(document.createElement("img")).attr({
          alt: "",
          src: firstThumbLink.attr("href")
        })
      )
  );

  var imgViewer = $("#"+settings.imgViewerId),
    bigPic = imgViewer.children("img");

  thumbLinks
  .click(function(e){
    e.preventDefault();
    var $this = $(this),
      target = $this.attr("href");
    if (bigPic.attr("src") == target) return;
    bigPic
      .attr("src",target);
  });

});

Notre galerie, bien qu'en apparence fonctionnelle, pêche par l'absence de marqueur graphique et sémantique indiquant à l'utilisateur la vignette en cours de visualisation.
Nous allons appliquer au lien actif une classe active en vue d'un stylage CSS particulier. L'information ne pouvant être transmise par le seul biais de la couleur (point de contrôle 1.4.1, WCAG 2.0), nous ajouterons également un attribut title explicite sur la vignette sélectionnée. Commençons par rajouter deux propriétés à l'objet settings contenant ces informations:

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer",
    activeClass: "active",
    activeTitle: "Photo en cours de visualisation"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0);

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
      .append(
        $(document.createElement("img")).attr({
          alt: "",
          src: firstThumbLink.attr("href")
        })
      )
  );

  var imgViewer = $("#"+settings.imgViewerId),
    bigPic = imgViewer.children("img");

  thumbLinks
    .click(function(e){
      e.preventDefault();
      var $this = $(this),
        target = $this.attr("href");
      if (bigPic.attr("src") == target) return;
      bigPic
        .attr("src",target);
    });

});

Ces nouvelles variables définies, il faut maintenant nous en servir pour mettre en évidence les vignettes cliquées, mais également la première vignette photo par défaut.
Afin de ne pas se répéter inutilement, nous allons définir la fonction highlight qui appliquera à l'élément qui l'appelle tout ce dont il a besoin pour se faire distinguer. Notons qu'il ne faudra pas oublier d'ôter à chaque clic la classe active et le title sur l'élément sélectionné auparavant.

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer",
    activeClass: "active",
    activeTitle: "Photo en cours de visualisation"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0),
    highlight = function(elt){
      thumbLinks.removeClass(settings.activeClass).removeAttr("title");
      elt.addClass(settings.activeClass).attr("title",settings.activeTitle);
    };

  highlight(firstThumbLink);

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
      .append(
        $(document.createElement("img")).attr({
          alt: "",
          src: firstThumbLink.attr("href")
        })
      )
  );

  var imgViewer = $("#"+settings.imgViewerId),
    bigPic = imgViewer.children("img");

  thumbLinks
    .click(function(e){
      e.preventDefault();
      var $this = $(this),
        target = $this.attr("href");
      if (bigPic.attr("src") == target) return;
      highlight($this);
      bigPic
        .attr("src",target);
    });

});

Le style graphique du lien possédant la classe active n'ayant pas encore été défini, aucune différence visuelle n'est constatable. Remédions à ce problème et profitons-en pour habiller quelque peu notre galerie photo:

body {background:#222;}
img {vertical-align:middle; border:none;}
#thumbs {overflow:auto; list-style:none; margin:30px; padding:0;}
#thumbs li {float:left;}
#thumbs a {display:block; padding:10px; outline:none;}
#thumbs a:hover, #thumbs a:focus {background:#fff;}
#thumbs a.active {background:#000;}
#viewer {width:700px; height:465px; margin-left:30px;}

Le lien actif est désormais mis en évidence par un cadre noir et l'ensemble a globalement meilleure allure.
Notre galerie est relativement convaincante et pourrait être utilisée telle quelle. Cela dit, le manque d'indication lors du chargement d'une nouvelle image amoindri considérablement l'expérience utilisateur. Ne nous arrêtons pas en si bon chemin et pallions sur-le-champ cette carence ergonomique.

Nous allons utiliser un gif animé tiré du générateur ad hoc Ajaxload pour représenter l'icône de chargement. Le loader choisi se présente comme tel:

Loader

Cette image n'étant appelée que lorsque JavaScript est activé, c'est via le DOM que nous allons la générer. Il nous faudra dès lors ajouter dans l'objet settings les valeurs des attributs nécessaires à la bonne formation de notre image:

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer",
    activeClass: "active",
    activeTitle: "Photo en cours de visualisation",
    loaderTitle: "Chargement en cours",
    loaderImage: "images/loader.gif"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0),
    highlight = function(elt){
      thumbLinks.removeClass(settings.activeClass).removeAttr("title");
      elt.addClass(settings.activeClass).attr("title",settings.activeTitle);
    },
    loader = $(document.createElement("img")).attr({
      alt: settings.loaderTitle,
      title: settings.loaderTitle,
      src: settings.loaderImage
    });

  highlight(firstThumbLink);

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
      .append(
        $(document.createElement("img")).attr({
          alt: "",
          src: firstThumbLink.attr("href")
        })
      )
  );

  var imgViewer = $("#"+settings.imgViewerId),
    bigPic = imgViewer.children("img");

  thumbLinks
    .click(function(e){
      e.preventDefault();
      var $this = $(this),
        target = $this.attr("href");
      if (bigPic.attr("src") == target) return;
      highlight($this);
      bigPic
        .attr("src",target);
    });

});

À ce stade, le loader a bien été créé dans le DOM mais n'est pas encore utilisé.
jQuery met à notre disposition le gestionnaire d'événement load() observant l'état de chargement d'un élément nommé. Lorsque celui-ci arrive à son terme, une méthode peut être déclenchée.
À chaque clic sur une nouvelle vignette, la marche à suivre consistera en 3 étapes successives:

  1. je remplace le contenu d'imgViewer par mon loader
  2. j'attache le gestionnaire d'événement à mon objet bigPic qui insérera une fois chargée la nouvelle photographie dans imgViewer
  3. j'actualise la valeur de l'attribut src de bigPic (grâce à la variable target précédemment définie) en fonction de la vignette cliquée

Ce dernier point ayant déjà été couvert, il ne nous reste plus qu'à écrire les étapes 1 et 2. Nous utiliserons la méthode html() pour introduire les contenus successifs dans imgViewer. Tant que nous y sommes, et plutôt que d'altérer les images de façon brutale, nous profiterons de l'événement fadeIn() pour faire apparaître les photographies de manière progressive. Le temps d'exécution du fondu est exprimé en millisecondes.

jQuery(function($){

  var settings = {
    thumbListId: "thumbs",
    imgViewerId: "viewer",
    activeClass: "active",
    activeTitle: "Photo en cours de visualisation",
    loaderTitle: "Chargement en cours",
    loaderImage: "images/loader.gif"
  };

  var thumbLinks = $("#"+settings.thumbListId).find("a"),
    firstThumbLink = thumbLinks.eq(0),
    highlight = function(elt){
      thumbLinks.removeClass(settings.activeClass).removeAttr("title");
      elt.addClass(settings.activeClass).attr("title",settings.activeTitle);
    },
    loader = $(document.createElement("img")).attr({
      alt: settings.loaderTitle,
      title: settings.loaderTitle,
      src: settings.loaderImage
    });

  highlight(firstThumbLink);

  $("#"+settings.thumbListId).after(
    $(document.createElement("p"))
      .attr("id",settings.imgViewerId)
      .append(
        $(document.createElement("img")).attr({
          alt: "",
          src: firstThumbLink.attr("href")
        })
      )
  );

  var imgViewer = $("#"+settings.imgViewerId),
    bigPic = imgViewer.children("img");

  thumbLinks
    .click(function(e){
      e.preventDefault();
      var $this = $(this),
        target = $this.attr("href");
      if (bigPic.attr("src") == target) return;
      highlight($this);
      imgViewer.html(loader);
      bigPic
        .load(function(){
          imgViewer.html($(this).fadeIn(250));
        })
        .attr("src",target);
    });

});

L'icône de chargement apparaît bien tant que l'UA n'a pas fini de récupérer l'image mais elle mériterait d'être positionnée plus élégamment. Nous allons centrer le loader verticalement et horizontalement grâce à un positionnement absolu par rapport à imgViewer qui servira de référent relatif. Ce loader peut être facilement ciblé grâce au sélecteur de sous-chaîne dans la valeur de l'attribut src. Notons que ce sélecteur d'attribut issu de CSS3 est implémenté par tous les navigateurs modernes, Internet Explorer 7 inclus.

body {background:#222;}
img {vertical-align:middle; border:none;}
#thumbs {overflow:auto; list-style:none; margin:30px; padding:0;}
#thumbs li {float:left;}
#thumbs a {display:block; padding:10px; outline:none;}
#thumbs a:hover, #thumbs a:focus {background:#fff;}
#thumbs a.active {background:#000;}
#viewer {position:relative; width:700px; height:465px; margin-left:30px;}
#viewer img[src*="loader"] {position:absolute; left:50%; top:50%; margin:-15px 0 0 -15px;}

Cette fois, nous y sommes! Notre galerie est aboutie et prête à l'emploi. Vous pouvez tester un exemple en ligne du résultat final.
Pour aller plus loin, nous pourrions faire de ce script un plugin jQuery, proposer des options de configuration, implémenter un système de navigation suivant/précédent et bien d'autres choses encore. N'hésitez pas à améliorer ce script et, pourquoi pas, poursuivre votre découverte de jQuery!

Photos: Dominique Cocagne