Minuteurs de cuisine
Des pâtes, un œuf et le riz à la fois : plusieurs minuteurs côte à côte, chacun avec sa pause, sa barre et son signal sonore. Le bon exercice pour gérer une liste d'éléments qui vivent tous en même temps.
- Gérer plusieurs minuteurs avec une seule boucle
- Calculer le temps restant à partir d'une heure de fin
- Produire un bip avec la Web Audio API
Pourquoi des minuteurs de cuisine ? En plein service, rien ne cuit au même rythme. Un seul minuteur ne suffit pas, et une pile de téléphones non plus.
Chaque minuteur retient son heure de fin, pas un nombre de secondes qui diminue. Le temps restant est la différence avec l'heure actuelle : même si l'onglet dort une minute, il reste juste. Une seule boucle parcourt toute la liste, plutôt qu'un setInterval par minuteur.
Le signal sonore est fabriqué à la volée par la Web Audio API, sans aucun fichier son.
Pourquoi en JavaScript simple ? Un tableau d'objets, une boucle et un oscillateur. La liste se trie pour mettre en haut ce qui sonne bientôt.
UN MINUTEUR = UNE HEURE DE FIN
On ne décompte pas : on compare l'heure de fin à maintenant.
let minuteurs = []; function ajouter(nom, secondes) { minuteurs.push({ id: crypto.randomUUID(), nom, total: secondes * 1000, fin: Date.now() + secondes * 1000, pause: null, fini: false }); }LE TEMPS RESTANT
En pause, on garde le reste figé ; sinon on le calcule.
const reste = m => m.pause !== null ? m.pause : Math.max(0, m.fin - Date.now());
PAUSE ET REPRISE
function basculer(m) { if (m.pause !== null) { m.fin = Date.now() + m.pause; m.pause = null; } else m.pause = reste(m); }UNE SEULE BOUCLE POUR TOUS
setInterval(() => { for (const m of minuteurs) { if (!m.fini && reste(m) === 0) { m.fini = true; bip(); } } afficher(); }, 250);LE BIP
Un oscillateur à 880 Hz, avec un volume qui monte puis retombe pour éviter le clic.
const audio = new AudioContext(); function bip() { const o = audio.createOscillator(), g = audio.createGain(), t = audio.currentTime; o.frequency.value = 880; g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.25, t + 0.02); g.gain.exponentialRampToValueAtTime(0.0001, t + 0.5); o.connect(g).connect(audio.destination); o.start(t); o.stop(t + 0.55); }
PIÈGE 1 : Un setInterval par minuteur
Mauvais : lancer un intervalle pour chaque minuteur. On finit par en oublier un, qui tourne encore après suppression.
Correct : une seule boucle qui parcourt la liste, et une liste qu'on filtre pour supprimer.
PIÈGE 2 : Décompter les secondes
Mauvais : restant -= 1 à chaque seconde. Dans un onglet en arrière-plan, les tics ralentissent et les pâtes sont trop cuites.
Correct : retenir l'heure de fin et calculer la différence à chaque passage.
PIÈGE 3 : Un son bloqué
Mauvais : créer l'AudioContext au chargement de la page. Les navigateurs le gardent muet tant que l'utilisateur n'a pas cliqué.
Correct : le créer, ou appeler resume, au premier clic sur un bouton.
- Une notification système avec l'API Notification quand l'onglet n'est pas visible.
- Des préréglages sauvegardés : la cuisson de vos pâtes préférées en un clic.
- Un bouton « + 1 min » pour prolonger une cuisson.
- Mettez le titre de l'onglet à jour avec le minuteur le plus proche : on le voit même sur un autre onglet.
- Gardez la liste dans localStorage avec les heures de fin : un rechargement ne perd rien.
La liste vit dans un useState ; un seul useEffect lance l'intervalle et le nettoie au démontage.
Un état « maintenant » mis à jour par l'intervalle force le rendu, et chaque reste se calcule à partir de lui.
L'AudioContext se garde dans un useRef, créé au premier clic.
Chargement des commentaires…