Créer un compte à rebours avec JavaScript
Publié le
Un compte à rebours est un composant que l'on retrouve sur de nombreux sites : pages d'événement, lancements de produit, Nouvel An. En JavaScript, plusieurs approches existent pour le mettre en place, chacune avec ses avantages et ses limites. Dans cet article, nous allons passer en revue ces méthodes avant de construire un exemple concret : un décompte jusqu'à minuit ce soir.
Calculer le temps restant
Quelle que soit la méthode choisie pour répéter les mises à jour, le calcul du temps restant repose toujours sur le même principe : soustraire la date actuelle à la date cible pour obtenir un écart en millisecondes, puis décomposer cet écart en unités lisibles.
function getTimeUntilMidnight() {
const now = new Date();
const midnight = new Date();
midnight.setHours(24, 0, 0, 0);
const diff = midnight - now; // Différence en millisecondes
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
return { hours, minutes, seconds };
}
L'appel setHours(24, 0, 0, 0) est une astuce pratique
: régler les heures à 24 sur la date du jour revient automatiquement à cibler minuit du
lendemain, sans avoir à gérer manuellement le changement de jour. Les opérateurs modulo
(%) et Math.floor
servent ensuite à décomposer l'écart total en heures, minutes et secondes.
Les trois méthodes disponibles
setInterval
setInterval exécute une fonction de façon répétée
selon un intervalle fixe exprimé en millisecondes. C'est l'approche la plus directe pour
mettre à jour un affichage toutes les secondes.
const intervalId = setInterval(function () {
const { hours, minutes, seconds } = getTimeUntilMidnight();
document.getElementById('hours').textContent = String(hours).padStart(2, '0');
document.getElementById('minutes').textContent = String(minutes).padStart(2, '0');
document.getElementById('seconds').textContent = String(seconds).padStart(2, '0');
}, 1000);
setInterval n'est pas parfaitement précis : si
l'onglet est en arrière-plan ou que le processeur est occupé, l'exécution peut être
retardée de quelques millisecondes. Sur la durée, ces micro-décalages s'accumulent. Pour
y remédier, on recalcule le temps restant à chaque tick en repartant de
new Date() plutôt qu'en décrémentant un compteur
interne : même si une exécution est retardée, l'heure affichée reste juste.
setTimeout récursif
Une alternative consiste à utiliser setTimeout de
façon récursive : plutôt que de programmer un rappel répété à intervalle fixe, on
planifie le prochain appel à la fin de chaque exécution.
function tick() {
const { hours, minutes, seconds } = getTimeUntilMidnight();
document.getElementById('hours').textContent = String(hours).padStart(2, '0');
document.getElementById('minutes').textContent = String(minutes).padStart(2, '0');
document.getElementById('seconds').textContent = String(seconds).padStart(2, '0');
setTimeout(tick, 1000);
}
tick();
La différence avec setInterval est subtile : le
prochain délai est calculé après la fin de l'exécution en cours. Si la fonction
met 20 ms à s'exécuter, le prochain appel sera planifié 1 000 ms plus tard, et non 980
ms. Pour un compteur à la seconde, cela n'a pas d'impact perceptible, mais c'est un
pattern plus sûr si la logique à exécuter à chaque tick est plus lourde.
requestAnimationFrame
requestAnimationFrame est conçu pour les animations :
le navigateur l'appelle avant chaque rafraîchissement d'écran, généralement 60 fois par
seconde. On peut l'utiliser pour un compte à rebours en vérifiant si la seconde a changé
avant de mettre à jour l'affichage.
let lastSecond = -1;
function tick() {
const { hours, minutes, seconds } = getTimeUntilMidnight();
if (seconds !== lastSecond) {
lastSecond = seconds;
document.getElementById('hours').textContent = String(hours).padStart(2, '0');
document.getElementById('minutes').textContent = String(minutes).padStart(2, '0');
document.getElementById('seconds').textContent = String(seconds).padStart(2, '0');
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
Les navigateurs suspendent
requestAnimationFrame lorsque l'onglet passe en
arrière-plan, ce qui réduit la consommation CPU. En revanche, le callback tourne en
permanence à 60 images par seconde même si l'affichage ne change qu'une fois par
seconde. La variable lastSecond limite les mises à
jour du DOM, mais cela reste l'option la plus gourmande des trois pour ce cas d'usage.
Quelle méthode choisir ?
Pour un compte à rebours à la seconde, setInterval
est le choix le plus adapté : simple à mettre en place, facile à arrêter avec
clearInterval, et peu coûteux en ressources. Le
setTimeout récursif est préférable si la logique
exécutée à chaque tick est lourde.
requestAnimationFrame
devient pertinent si le compteur doit s'intégrer dans une animation plus complexe ou
afficher une précision inférieure à la seconde.
Exemple pratique : décompte jusqu'à minuit avec setInterval
Voici l'implémentation complète. On appelle
updateDisplay() une première fois avant de lancer
l'intervalle pour éviter une seconde de blanc à l'affichage. La méthode
padStart(2, '0') garantit que les valeurs inférieures
à 10 s'affichent toujours sur deux chiffres.
function getTimeUntilMidnight() {
const now = new Date();
const midnight = new Date();
midnight.setHours(24, 0, 0, 0);
const diff = midnight - now;
return {
hours: Math.floor(diff / (1000 * 60 * 60)),
minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((diff % (1000 * 60)) / 1000)
};
}
function updateDisplay() {
const { hours, minutes, seconds } = getTimeUntilMidnight();
document.getElementById('hours').textContent = String(hours).padStart(2, '0');
document.getElementById('minutes').textContent = String(minutes).padStart(2, '0');
document.getElementById('seconds').textContent = String(seconds).padStart(2, '0');
}
updateDisplay();
setInterval(updateDisplay, 1000);
Les limites globales à connaître
Au-delà des différences entre méthodes, plusieurs contraintes s'appliquent à tout compte à rebours JavaScript côté client.
La première est la dépendance à l'horloge locale :
new Date()
lit l'heure du système de l'utilisateur. Si cette horloge est mal réglée, le compteur
sera faux. Pour des applications critiques (ventes flash, enchères), il faut
synchroniser l'heure avec un serveur.
La deuxième concerne les onglets en arrière-plan. Les navigateurs limitent la fréquence
d'exécution des timers dans les onglets inactifs. Notre approche de recalcul sur
new Date() compense ce problème : dès que
l'utilisateur revient sur l'onglet, l'heure affichée est correcte, même si des ticks ont
été sautés.
La troisième est la dérive sur de longues périodes. Un compteur qui décrémente un entier interne accumulera inévitablement du retard. Là encore, repartir de l'heure réelle à chaque tick résout entièrement ce problème.