Créer un compteur de temps (jours, heures, minutes) dynamique en Javascript avec Stimulus

Publié le 24 Mar 2021

Avant la sortie d’un nouveau produit ou la date d’un événement important, il est recommandé de communiquer pour faire du teasing et donner envie à ses prospects d’en découvrir davantage le jour venu. Un bon moyen d’attirer l’attention sur votre site serait par exemple, de mettre en place sur la page d’accueil un compteur calculant en temps réel le nombre de jours, d’heures et de minutes restants ! Voyons un peu comment faire en utilisant Javascript et le framework Stimulus.

Pour celles et ceux qui ne connaîtraient pas encore Stimulus, nous vous laissons découvrir notre article d’introduction à l’utilisation de Stimulus, qui vous montre les bases et vous explique les avantages de ce framework Javascript léger, facile à prendre en main et pratique pour organiser ses fichiers Javascript.

Le design du compteur

Pour ce tutoriel, nous avons choisi de mettre en place des compteurs ronds qui se rempliront en fonction de l’état d’avancement du temps. Pour cela, nous utiliserons la balise HTML <canvas>, qui permet de dessiner des graphes en tous genres, de manipuler des images ou de créer des animations. 

A l’intérieur de chacun de nos graphes se trouvera un texte indiquant la valeur de celui-ci. Les graphes se mettront à jour tant que l’utilisateur navigue sur la page, pour que le résultat soit plus impactant et attire l’attention du lecteur sur cette partie de la page.

Le développement du compteur dynamique

Comme nous l’évoquions précédemment, nous utiliserons Stimulus pour développer notre compteur dynamique en Javascript. Il offre des fonctionnalités très pratiques pour jouer avec des variables, déclencher des fonctions lors d’événements précis etc.

La mise en place du controller Javascript

Pour commencer, nous allons ajouter dans le fichier HTML ou Twig consacré à notre page d’accueil, un bloc qui contiendra notre compteur.


        <div class="counter-canvas"></div>
    

Ensuite, nous créons le controller counter_controller dans lequel nous placerons un peu plus tard la fonctionnalité du compteur. 


        import {Controller} from "stimulus"

export default class extends Controller {
    connect() {
        console.log('controller du compteur : OK');
    }
}
    

Pour appeler ce controller dans la vue, il suffit d’ajouter à notre bloc l’attribut  data-controller="counter".


        <div class="counter-canvas" data-controller="counter"></div>
    

Au rechargement de la page, on verra alors s’afficher en console le message inscrit précédemment dans le controller. Tout fonctionne, il est maintenant temps de passer aux choses sérieuses !

Calculer le temps restant entre des dates

Pour réaliser notre compteur de temps, nous aurons besoin de trois éléments : la date de début, la date de fin et la date actuelle ! Nous calculerons les distances entre ces dates afin de déterminer ensuite le nombre de jours, d’heures et de minutes restants avant la fin du temps imparti.

Pour optimiser la construction de notre fichier, nous allons séparer notre logique en plusieurs fonctions distinctes. Nous appellerons ensuite ces fonctions aux bons endroits. L’appel à la fonction principale permettant de créer les compteurs sera placé dès l’état de connexion du document.


        import {Controller} from "stimulus"


export default class extends Controller {
    connect() {
        this.createCanvasCounter();
    }

    createCanvasCounter() {
    }
}
    

Maintenant que vous connaissez la logique globale, commençons par ajouter les différentes données concernant nos dates en tant que paramètres dans notre HTML.


        <div class="counter-canvas" data-controller="counter"
     data-counter-begin-date-value="2021-02-25T00:00:00"
     data-counter-end-date-value="2021-04-12T00:00:00">
</div>
    

Pour permettre à notre compteur de se mettre à jour automatiquement toutes les minutes, nous ajoutons aussi un paramètre concernant l'intervalle de rafraîchissement du compteur.


        <div class="counter-canvas" data-controller="counter"
     data-counter-begin-date-value="2021-02-25T00:00:00"
     data-counter-end-date-value="2021-04-12T00:00:00"
     data-counter-refresh-interval-value="60000">
</div>
    

Dans le controller Javascript, nous allons pouvoir récupérer les différentes valeurs que nous venons de renseigner côté HTML et ainsi mettre en place les différents calculs permettant de savoir combien de jours, d'heures et de minutes il y a entre nos deux dates de début et de fin.


        import {Controller} from "stimulus"

export default class extends Controller {
    static values = {refreshInterval: Number, beginDate: String, endDate: String, size: Number, line: Number}

    connect() {
        this.createCanvasCounter();
    }

    createCanvasCounter() {
        let beginDate = new Date(this.beginDateValue.replace(/\s/, 'T')+'Z').getTime();
        let countDownDate = new Date(this.endDateValue.replace(/\s/, 'T')+'Z').getTime();
        let now = new Date().getTime();
        // Find the max distance between begin date and and date
        let maxDistance = countDownDate - beginDate;
        // Find the distance between now and the count down date
        let distance = countDownDate - now;
        // Set max values for canvas creation
        let maxDays = Math.floor(maxDistance / (1000 * 60 * 60 * 24));
        // Time calculations for days, hours and minutes
        let days = Math.floor(distance / (1000 * 60 * 60 * 24));
        let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
        let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));

        // Time calculations for percentages in canvas for days, hours and minutes
        let daysPercentage = Math.floor(100 - ((days * 100) / maxDays));
        let hoursPercentage = Math.floor(100 - ((hours * 100) / 24));
        let minutesPercentage = Math.floor(100 - ((minutes * 100) / 60));
    }
}
    

On déclare trois variables beginDatecountDownDate et now correspondant respectivement aux valeurs numériques de la date de début, la date de fin et la date actuelle. Ensuite, on crée une variable maxDistance qui équivaut à l'écart numérique maximum entre la date de fin et la date de début. On ajoute également une variable distance qui détermine l'écart numérique entre la date de fin et la date actuelle. Puis on passe aux calculs des temps restants.

  • maxDays : retourne la valeur numérique maximum du nombre de jours entre la date de fin et la date de début.
  • days : retourne le nombre de jours restants.
  • hours : retourne le nombre d'heures restantes.
  • minutes : retournes le nombre de minutes restantes.

La génération des Canvas

Maintenant que l'on dispose de toutes les informations dont on a besoin, on va passer à la création de la méthode qui nous permettra de générer chacun des canvas


        /*
 * Generate a Canvas function
 */
generateCanvas(timeType, translation, id, percentage) {
    // Canvas Container
    let canvasContainer = id;
    // Canvas options
    let options = {
        size: this.sizeValue,
        percent: percentage,
        lineWidth: this.lineValue,
        rotate: 0
    }

    // Create canvas element
    let canvas = document.createElement('canvas');
    if (typeof (G_vmlCanvasManager) !== 'undefined') {
        G_vmlCanvasManager.initElement(canvas);
    }

    // Declare context of the canvas and sizes
    let ctx = canvas.getContext('2d');
    canvas.width = canvas.height = options.size;
    let radiusCalc = (options.size - options.lineWidth) / 2;

    // Put the generated canvas into its container
    canvasContainer.innerHTML = "<div><span class='time'>" + timeType + "</span>" + "<span>" + translation + "</span></div>";
    canvasContainer.appendChild(canvas);

    ctx.translate(options.size / 2, options.size / 2); // change center
    ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI); // rotate -90 deg
}
    

On ajoute également une méthode permettant de dessiner chacun des canvas en 2D.


        drawCircle(context, color, lineWidth, percent, radius) {
    percent = Math.min(Math.max(0, percent || 1), 1);
    context.beginPath();
    context.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
    context.strokeStyle = color;
    context.lineCap = 'round'; // butt, round or square
    context.lineWidth = lineWidth
    context.stroke();
}
    

On ajoute l'appel à cette fonction dans notre fonction précédente ( generateCanvas) :


        // Draw the circles
this.drawCircle(ctx, 'rgba(255, 255, 255, 1)', options.lineWidth, 100 / 100, radiusCalc);
this.drawCircle(ctx, 'rgba(0, 0, 0, 0.5)', options.lineWidth, options.percent / 100, radiusCalc);
    

Comme on en aura besoin pour créer plusieurs canvas, cette méthode permet d'optimiser notre code car elle sera appelée plusieurs fois dans notre méthode principale, comme suit :


        /*
 * Create Canvas Counter
 */
createCanvasCounter() {
    let beginDate = new Date(this.beginDateValue.replace(/\s/, 'T')+'Z').getTime();
    let countDownDate = new Date(this.endDateValue.replace(/\s/, 'T')+'Z').getTime();
    let now = new Date().getTime();
    // Find the max distance between begin date and and date
    let maxDistance = countDownDate - beginDate;
    // Find the distance between now and the count down date
    let distance = countDownDate - now;
    // Set max values for canvas creation
    let maxDays = Math.floor(maxDistance / (1000 * 60 * 60 * 24));
    // Time calculations for days, hours and minutes
    let days = Math.floor(distance / (1000 * 60 * 60 * 24));
    let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));

    // Time calculations for percentages in canvas for days, hours and minutes
    let daysPercentage = Math.floor(100 - ((days * 100) / maxDays));
    let hoursPercentage = Math.floor(100 - ((hours * 100) / 24));
    let minutesPercentage = Math.floor(100 - ((minutes * 100) / 60));

    // Find the elements in front-end
    let daysItem = document.getElementById("days");
    let hoursItem = document.getElementById("hours");
    let minutesItem = document.getElementById("minutes");
    let daysItemTranslation = daysItem.getAttribute('data-counter-translation');
    let hoursItemTranslation = hoursItem.getAttribute('data-counter-translation');
    let minutesItemTranslation = minutesItem.getAttribute('data-counter-translation');

    this.generateCanvas(days, daysItemTranslation ,daysItem, daysPercentage);
    this.generateCanvas(hours, hoursItemTranslation, hoursItem, hoursPercentage);
    this.generateCanvas(minutes, minutesItemTranslation, minutesItem, minutesPercentage);
}
    

Au rafraîchissement de la page, on aura désormais trois canvas affichés pour le nombre de jours, d'heures et de minutes restants avant la fin de notre événement.

Le dernier élément important à prendre en compte, c'est l'erreur qui peut s'afficher lorsque notre compteur passe la date de fin ! Il serait dommage que tous les canvas ne soient pas à 0 au moment de l'événement ! Voyons donc comment déclencher ou stopper les compteurs en fonction de certaines conditions.


        startRefreshing() {
    this.refreshTimer = setInterval(() => {
        this.createCanvasCounter()
    }, this.refreshIntervalValue);
}

stopRefreshing() {
    if (this.refreshTimer) {
        clearInterval(this.refreshTimer);
    }
}
    

Ces deux méthodes permettront de déclencher le calcul des temps et d'arrêter nos compteurs en fonction de l'intervalle choisie pour le rafraîchissement (pour rappel, nos compteurs se rafraîchissent toutes les minutes). 

Il ne nous reste plus qu'à y faire appel aux moments des connexions et déconnexions des controllers :


        connect() {
    this.createCanvasCounter();
    if (this.hasRefreshIntervalValue) {
        this.startRefreshing();
    }
}

disconnect() {
    this.stopRefreshing()
}
    

Et voici donc comment créer un compteur de temps en Javascript avec Stimulus ! Il ne vous reste plus qu'à ajouter une petite partie de style en CSS et le tour est joué !