Aller au contenu
← 1001 Widgets
N° 036 · ●●○

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.

JS
Ce qu'on apprend
  • 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 ce langage

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.

Comment ça marche
  1. 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 });
    }
  2. 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());
  3. PAUSE ET REPRISE

    function basculer(m) {
      if (m.pause !== null) { m.fin = Date.now() + m.pause; m.pause = null; }
      else m.pause = reste(m);
    }
  4. UNE SEULE BOUCLE POUR TOUS

    setInterval(() => {
      for (const m of minuteurs) {
        if (!m.fini && reste(m) === 0) { m.fini = true; bip(); }
      }
      afficher();
    }, 250);
  5. 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èges

PIÈGE 1 : Un setInterval par minuteur

Piège

Mauvais : lancer un intervalle pour chaque minuteur. On finit par en oublier un, qui tourne encore après suppression.

Fix

Correct : une seule boucle qui parcourt la liste, et une liste qu'on filtre pour supprimer.

PIÈGE 2 : Décompter les secondes

Piège

Mauvais : restant -= 1 à chaque seconde. Dans un onglet en arrière-plan, les tics ralentissent et les pâtes sont trop cuites.

Fix

Correct : retenir l'heure de fin et calculer la différence à chaque passage.

PIÈGE 3 : Un son bloqué

Piège

Mauvais : créer l'AudioContext au chargement de la page. Les navigateurs le gardent muet tant que l'utilisateur n'a pas cliqué.

Fix

Correct : le créer, ou appeler resume, au premier clic sur un bouton.

PERSONNALISER
  • 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.
OPTIMISATION
  • 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.
VERS REACT

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.

Commentaires
0/1000

Chargement des commentaires…