Galerie d'image en JS

Galerie d'image en JS

Pour ce nouveau site j'ai eu besoin de réaliser une galerie d'image en JavaScript. Habituellement je trouve toujours une petite librairie pour le faire à ma place. Ces librairies sont relativement lourdes, et viennent avec quelques dépendances, notamment jQuery. Afin de faire plus léger que ça, j'ai créer mon propre système. Je vais donc vous expliquer ce que j'ai fait et comment. Afin de comprendre tout ce que je vais faire, les bases de JS s'imposent. Autrement ça reste du HTML et du CSS classique.

Codons

A la base j'ai juste une liste de miniatures d'images sur lesquelles je met un lien vers l'image complète. La structure est la suivante :

<ul class="gallery">
  <li>
    <a href="https://media.balandavid.com/david-balan/article/Full-frame-less/high.jpg">
      <img src="https://media.balandavid.com/david-balan/article/Full-frame-less/thumb.jpg" alt="May the force be with you">
    </a>
  </li>
  <li>
    <a href="https://media.balandavid.com/david-balan/article/DSCF0145/high.jpg">
      <img src="https://media.balandavid.com/david-balan/article/DSCF0145/thumb.jpg" alt="Lego batman">
    </a>
  </li>
  <li>
    <a href="https://media.balandavid.com/david-balan/article/cake-logo/high.png">
      <img src="https://media.balandavid.com/david-balan/article/cake-logo/thumb.png" alt="">
    </a>
  </li>
</ul>

Je souhaite maintenant que chaque clique sur une miniature soit interprété pour l'ouvrir en plein écran tout en restant sur la page courante.

Pour cela je vais commencer par définir un ensemble de fonctions outils. Pour information j'utilise ici de l'ES6. Un petit coup de Babel vous le transformera en ES5 si besoin.

const $ = (s, c=document) => c.querySelector(s),
      $$ = (s, c=document) => c.querySelectorAll(s),
      a = n => Array.from(n);

On va donc commencer par identifier tous les éléments utilisant la classe .gallery, lister tous les liens (a) et gérer l’événement click en empêchant que ce dernier ne se propage.

a($$('.gallery')).forEach(elm => {
  a($$('a', elm)).forEach(a => {
    a.addEventListener('click', (e) => { 
      e.preventDefault();
    });
  });
});

Maintenant il va falloir gérer l’événement click en affichant l'image en question. Nous voulons aussi pouvoir naviguer dans la liste d'image, donc on passera a cette fonction la liste des images et la position de l'image à afficher maintenant. Pour cela je vais modifier légèrement le code que l'on viens d'écrire et y ajouter une fonction showImage, elle sera en charge de modifier deux variables globales et de mettre à jour le dom pour afficher l'image en question.

let images = [];
let main_position = 0;

const update = () => {
  // change the dom to show the image
},showImage = (list, position=0) => {
  images = list;
  main_position = position >= images.length ? 0 : position < 0 ? images.length - 1 : position;
  update();
}

a($$('.gallery')).forEach(elm => {
  const links = a($$('a', elm));
  links.forEach((a, i) => {
    a.addEventListener('click', (e) => { 
      e.preventDefault();
      showImage(links, i);
    });
  });
});

Cela étant fait, nous allons maintenant nous attaquer à la modification du DOM. Pour cela la fonction update va devoir déterminer si les éléments existes, sinon les créer, et ensuite mettre l’ensemble à jour.

const c = (t, a, i) => {
  t = document.createElement(t);
  a && Object.keys(a).forEach((k) => { t[k] = a[k]; });
  i && i.appendChild(t);
  return t;
}

let slider = null;

const create = () => {
  slider = c('div', {className: 'slider'}, $('body'));
  c('img', {className: 'full'}, slider);
},update = () => {
  if(!slider) {
    create();
  }
  $('.full', slider).src = images[main_position].href;
}

Sans style, l'image complète s'affichera en pied de page. N'étant pas spécialement doué en CSS je vous propose la version suivante :

.slider {
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.75);
  z-index: 40;
  padding: 20px;
  display: flex;
}
.slider .full {
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  display: block;
}

Nous avons maintenant besoin de faire les boutons suivants et précédent permettant de passer d'une image à l'autre. Il nous suffira d'incrémenter ou décrémenter la position et d'appeler la fonction showImage qui assurera le rafraîchissement de la page. Pour la création des boutons, tout ce passera dans la méthode create.

const create = () => {
  slider = c('div', {className: 'slider'}, $('body'));
  c('img', {className: 'full'}, slider);
  const next = c('button', {className: 'next'}, slider),
        previous = c('button', {className: 'previous'}, slider);

  next.addEventListener('click', e => e.stopPropagation() || showImage(images, main_position + 1));
  previous.addEventListener('click', e => e.stopPropagation() || showImage(images, main_position - 1));
};

Maintenant les boutons ont un placement un peut limité, donc on ajouter un peu de CSS.

.slider button {
  flex:  0;
  position: fixed;
  top: 0;
  bottom: 0;
  width: 50%;
  border: none;
  background: none;
}
.slider button.next {
  right: 0;
}
.slider button.previous {
  left: 0;
}

Dernière étape, un bouton close, car une fois ouvert, l'accès au reste de la page deviens très limité. On vas donc ajouter un autre bouton qui aura pour rôle de masquer le slider. Il faudra juste bien penser de le ré-afficher ensuite.

const create = () => {
  slider = c('div', {className: 'slider'}, $('body'));
  c('img', {className: 'full'}, slider);
  const next = c('button', {className: 'next', innerHTML: '>'}, slider),
        previous = c('button', {className: 'previous', innerHTML: '<'}, slider),
        close = c('button', {className: 'close', innerHTML: 'X'}, slider);

  next.addEventListener('click', e => e.stopPropagation() || showImage(images, main_position + 1));
  previous.addEventListener('click', e => e.stopPropagation() || showImage(images, main_position - 1));
  close.addEventListener('click', e => e.stopPropagation() || (slider.style.display='none'));
},update = () => {
  if(!slider) {
    create();
  }
  $('.full', slider).src = images[main_position].href;
  slider.style.display='flex';
}

Avec le style suivant

.slider button.close {
  right: 0;
  bottom: initial;
  background: white;
  color: black;
  width: auto;
}

Aller plus loin

  • Faire un CSS propre (Super CSS man)
  • Mettre des images sur les boutons (Super CSS man)
  • Gérer les événements touch pour les mobiles (touchend)
  • Gérer les événement clavier (keypress)
  • Gérer plusieurs résolution d'écran et qualité d'images (<picture>)
  • Gérer les cas ou il n'y a qu'une image

Bilan

Voilà, vous avez un slider d'image qui tiens en moins de 50 lignes de JS. Vous pouvez facilement l'adapter et le ré-utiliser sans utiliser une librairie monstrueusement grosse et inutile pour ça. Minifié on arrive à 580 octets, et il y a plusieurs solutions pour faire encore mieux.

Code

Je vous ai fait un gist du code complet.