Le web déborde de petites librairies en Javascript qui fournissent toutes les fonctionnalités imaginables. Mais quand on utilise un framework web, l’intégration d’une librairie conçue dans un contexte « Vanilla JS » n’est pas toujours évidente.

Ici on va s’intéresser à un composant très basique qui se trouve un peu partout : les spinner. Ce sont des simples animations qui apparaissent pour indiquer à l’utilisateur que l’application travaille et qu’il faut patienter. L’implémentation d’un spinner est souvent assez simple, nécessitant seulement quelques balises HTML et un peu de magie CSS.

La librairie Spin.js est un bon exemple. Elle est légère, simple, paramétrable, et sans dépendances. Elle est même livrée avec les définitions Typescript ! Voici ce à quoi il ressemble avec les paramètres de base :

Concrètement, pour l’utiliser, il faut :

  • Inclure/importer le script spin.js, ainsi que son CSS
  • Instancier l’objet Spinner avec les options souhaitées
  • Fournir un élément HTML auquel le Spinner peut s’attacher

Cependant, si on utilise un framework web, c’est pour que tout soit aussi simple que possible. Avec Vue, il serait plus confortable d’emballer ces détails dans un composant, ce qui permettrait de simplement insérer un <spinner/> à l’endroit souhaité.

Implémentation de base

D’abord, on installe la librairie :

npm install --save spin.js

On crée ensuite le composant Spinner.vue. La première chose qu’il faut est l’élément auquel le spinner va s’attacher :

<template>
  <div id="spinner"></div>
</template>

Il faut également inclure la feuille de style du spinner, sans quoi on n’aura pas l’animation :

<style lang="scss">
@import '~spin.js/spin.css';
</style>

La logique du composant est assez simple. On crée le spinner lors du mount, et on l’arrête sur unmount :

export default defineComponent({
  data: () => ({
    // L’instance du spinner pour ce composant
    spinner: null as Spinner | null,
  }),

  mounted() {
    this.spinner = new Spinner().spin(document.getElementById('spinner') || undefined);
  },

  unmounted() {
    this.spinner?.stop();
  },
});

Amélioration du composant

On a maintenant un composant qui fonctionne. Cependant, en l’état il manque plusieurs choses qui le rendrait plus souple et robuste.

Paramétrage et valeurs par défaut

Le spinner peut être paramétré en passant un objet de type SpinnerOptions lors de sa création. Il serait intéressant d’exposer cette interface dans les props du composant, pour que nos utilisateurs puissent modifier les options. A la même occasion, on pourrait ajouter un paramétrage par défaut :

const defaultOptions: SpinnerOptions = {
  color: '#112233',
  lines: 2
};

export default defineComponent({
  props: {
    opts: {
      type: Object as PropType<SpinnerOptions>,
    },
  },
  mounted() {
    const opts = Object.assign({}, defaultOptions, this.opts);
    this.spinner = new Spinner(opts).spin(document.getElementById('spinner') || undefined);
  }
});
<spinner :opts="{lines: 4}" />

ID unique

Un problème potentiel pourrait arriver si jamais on avait 2 spinners qui s’affichaient en même temps : l’ID de la div à laquelle est attaché le spinner. Heureusement, Vue nous fournit la solution : au lieu de demander au document l’élément ayant un certain ID, on le récupère directement via une référence.

<template>
  <div ref="spinDiv"></div>
</template>
export default defineComponent({
  setup() {
    return {
      spinDiv: ref<HTMLElement>()
    }
  },
  mounted() {
    this.spinner = new Spinner().spin(unref(this.spinDiv));
  },
});

Paramétrage de la div

La div qui contient le spinner n’a pas d’attribut particulier (et le contenu injecté par Spin.js est en position absolute) – il est donc invisible et sans effet sur le layout (de taille 0x0). Cependant, on pourrait dans certains cas vouloir lui affecter un style – par exemple, ajouter une hauteur ou largeur pour qu’il écarte ses éléments voisins.

Heureusement, pour ce faire on n’a besoin de rien en particulier. Selon la documentation de Vue, les attributs sur un composant sont passés directement à la balise racine de celui-ci. Il suffit donc d’ajouter la déclaration de classe ou de style à la balise du spinner :

<spinner style="height: 20px;"/>

Conclusion

Nous avons vu comment créer un composant dans Vue.JS V3 qui entoure une librairie écrite en Vanilla JS. Son paramétrage est exposé et son cycle de vie géré.

La solution complète se trouve ici.