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
beginDate
,
countDownDate
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é !