Un écran affichant un décompte numérique

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);
00 Heures
00 Minutes
00 Secondes

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.

Sources

Mots clés

HTML JavaScript

Coaching

À partir de 29€
  • Optimisation de code
  • Bonnes pratiques de développement
  • Montée en compétences rapide
Réserver une session
Composants internes d'un ordinateur Pourquoi mon ordinateur est-il lent ? CPU, RAM et mémoire expliqués

Qu'est-ce qui se passe réellement sous le capot de votre PC ? Découvrez comment l'articulation entre le CPU, la RAM et le stockage définit la vitesse de votre machine et l'origine matérielle des lenteurs.

Ecran d'ordinateur affichant une boite mail SPF : le réglage DNS qui décide si vos emails arrivent ou se font rejeter

Vous recevez un message « Undelivered Mail Returned to Sender » ou « SPF check failed » ? Découvrez comment fonctionne SPF, pourquoi vos emails sont rejetés et comment corriger votre configuration DNS.