Créer un compte à rebours pour les fêtes en JavaScript
Chaque décembre, les développeurs font face à la même demande : créer un compte à rebours jusqu’à Noël, le Nouvel An ou une autre fête. Cela semble simple jusqu’à ce que votre minuteur dérive de plusieurs minutes, se casse lorsque les utilisateurs changent d’onglet, ou affiche des nombres négatifs après minuit.
Cet article vous montre comment créer un compte à rebours JavaScript fiable qui gère correctement ces cas limites. Vous apprendrez pourquoi recalculer le temps à chaque tick est préférable à décrémenter un compteur, comment gérer correctement les fuseaux horaires en JavaScript, et comment éviter les pièges courants qui font trébucher la plupart des implémentations.
Points clés à retenir
- Recalculez le temps restant à chaque tick en utilisant
Date.now()au lieu de décrémenter un compteur pour éviter la dérive du minuteur - Utilisez
new Date(year, month, day)pour créer des dates dans le fuseau horaire local de l’utilisateur sans bibliothèques externes - Stockez et effacez toujours les ID d’intervalle pour éviter les fuites mémoire, surtout dans les applications monopages
- Construisez une résilience contre la limitation des onglets du navigateur en basant les calculs sur les différences de temps réelles
Pourquoi les compteurs décrémentés échouent
L’approche naïve pour créer un widget JavaScript de compte à rebours pour les fêtes ressemble à ceci : définir une variable avec le nombre de secondes restantes, puis soustraire une seconde à chaque fois en utilisant setInterval. Cela échoue en pratique.
Le problème est la dérive du minuteur setInterval. Les minuteurs JavaScript ne garantissent pas une exécution précise. Lorsqu’un utilisateur change d’onglet, les navigateurs limitent les minuteurs pour économiser des ressources—se déclenchant parfois une fois par seconde, parfois une fois par minute. Votre compteur continue de décrémenter comme si le temps s’écoulait normalement, mais ce n’est pas le cas.
La solution est simple : recalculez le temps restant à chaque tick en comparant Date.now() avec votre date cible. De cette façon, même si votre minuteur se déclenche en retard, l’heure affichée reste précise.
Configurer la date cible
Pour un compte à rebours de fête, vous voulez généralement minuit à une date calendaire spécifique dans le fuseau horaire local de l’utilisateur. Voici comment configurer cela correctement :
function getTargetDate(month, day) {
const now = new Date()
const year = now.getFullYear()
// If the holiday has passed this year, target next year
const target = new Date(year, month - 1, day, 0, 0, 0)
if (target <= now) {
target.setFullYear(year + 1)
}
return target
}
const christmasTarget = getTargetDate(12, 25)
L’utilisation de new Date(year, month, day) crée automatiquement une date dans le fuseau horaire local de l’utilisateur. Cela gère les fuseaux horaires JavaScript sans nécessiter de bibliothèque—le compte à rebours affiche le temps jusqu’à minuit le 25 décembre où que se trouve l’utilisateur.
Note sur l’heure d’été : l’objet Date de JavaScript gère automatiquement les transitions d’heure d’été lorsque vous construisez les dates de cette manière. Le compte à rebours reste précis même lorsque les horloges changent.
La logique de base du compte à rebours
Voici une implémentation prête pour la production qui résout les problèmes courants :
function createCountdown(targetDate, onUpdate, onComplete) {
let intervalId = null
function calculateRemaining() {
const now = Date.now()
const difference = targetDate.getTime() - now
// Prevent negative values
if (difference <= 0) {
clearInterval(intervalId)
onComplete()
return null
}
return {
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / (1000 * 60)) % 60),
seconds: Math.floor((difference / 1000) % 60)
}
}
function tick() {
const remaining = calculateRemaining()
if (remaining) {
onUpdate(remaining)
}
}
// Run immediately, then every second
tick()
intervalId = setInterval(tick, 1000)
// Return cleanup function
return () => clearInterval(intervalId)
}
Cette approche résout plusieurs problèmes à la fois :
- Aucune dérive : le temps est recalculé à chaque tick
- Aucune valeur négative : le compte à rebours s’arrête et efface l’intervalle lorsqu’il est terminé
- Nettoyage approprié : la fonction retournée vous permet d’arrêter le minuteur quand nécessaire
- Résilience à la limitation des onglets : même si les ticks sont retardés, l’heure affichée reste correcte
Discover how at OpenReplay.com.
Connexion au DOM
Connectez le compte à rebours à votre HTML :
const display = document.getElementById('countdown')
const cleanup = createCountdown(
christmasTarget,
({ days, hours, minutes, seconds }) => {
display.textContent = `${days}j ${hours}h ${minutes}m ${seconds}s`
},
() => {
display.textContent = "C'est arrivé !"
}
)
// Call cleanup() when navigating away in a SPA
Gérer les cas limites
Déjà passé : la fonction getTargetDate bascule automatiquement sur l’année suivante si la fête est passée.
Effacer les intervalles : stockez toujours l’ID d’intervalle et effacez-le lorsque le compte à rebours se termine. Ne pas le faire provoque des fuites mémoire, surtout dans les applications monopages.
Accessibilité : envisagez d’ajouter aria-live="polite" à votre conteneur de compte à rebours pour que les lecteurs d’écran annoncent les mises à jour sans être perturbateurs.
Perspectives d’avenir
L’API Temporal à venir rendra la gestion des dates et heures en JavaScript beaucoup plus facile, avec un support explicite des fuseaux horaires intégré. Jusqu’à ce qu’elle soit disponible dans tous les navigateurs, les modèles présentés ici restent le choix fiable.
Conclusion
Créez votre widget JavaScript de compte à rebours pour les fêtes en utilisant des calculs de différence de temps plutôt que des compteurs décrémentés. Votre minuteur restera précis indépendamment de la limitation du navigateur, du changement d’onglet ou des transitions d’heure d’été—et vous éviterez les maux de tête de débogage que créent les approches plus simples.
FAQ
Les navigateurs limitent les minuteurs JavaScript dans les onglets en arrière-plan pour économiser des ressources. Si vous décrémentez un compteur chaque seconde, le décompte continue comme si le temps s'écoulait normalement pendant que l'onglet était inactif. Au lieu de cela, recalculez le temps restant à chaque tick en utilisant Date.now() comparé à votre date cible. Cela garantit la précision quelle que soit la fréquence réelle de déclenchement du minuteur.
Utilisez le constructeur Date avec des arguments numériques comme new Date(year, month, day) pour créer automatiquement des dates dans le fuseau horaire local de l'utilisateur. Cette approche ne nécessite aucune bibliothèque externe et garantit que chaque utilisateur voit un compte à rebours jusqu'à minuit dans son propre fuseau horaire plutôt qu'une heure UTC fixe.
Les fuites mémoire se produisent lorsque setInterval continue de s'exécuter après la fin du compte à rebours ou lorsque les utilisateurs naviguent ailleurs dans les applications monopages. Stockez toujours l'ID d'intervalle retourné par setInterval et appelez clearInterval lorsque le compte à rebours se termine ou lorsque le composant est démonté. Retournez une fonction de nettoyage depuis votre créateur de compte à rebours pour une élimination facile.
L'API Temporal simplifiera la gestion des dates et heures avec un support explicite des fuseaux horaires et une sémantique plus claire. Cependant, le principe fondamental de recalculer les différences de temps plutôt que de décrémenter des compteurs restera pertinent. Jusqu'à ce que Temporal soit disponible dans tous les navigateurs, les modèles de cet article fournissent une solution multi-navigateurs fiable.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.