Mini-applications et mini-jeux

Compte à rebours d’activité

Minuteur avec réglage des minutes et prise en charge de la pause, de la reprise et de la réinitialisation.

HTMLCSSJavaScriptMinuteur
Après activation de JavaScript, ce cas peut être modifié, exécuté et enregistré. Code source fixe ci-dessous :
<!doctype html>
<html lang="fr" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Compte à rebours d’activité</title>
  <style>
    * { box-sizing: border-box; }
    :root { color-scheme: dark; }
    body { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: 24px; color: #fff8e8; font-family: "Avenir Next", "Microsoft YaHei", sans-serif; background: radial-gradient(circle at 20% 20%, rgba(255,125,75,.17), transparent 32rem), #17151c; }
    .timer-card { width: min(680px, 100%); padding: clamp(28px, 6vw, 56px); border: 1px solid #38333f; border-radius: 34px; background: #211e27; box-shadow: 0 32px 90px rgba(0,0,0,.38); text-align: center; }
    .eyebrow { color: #ff8258; font-size: 11px; font-weight: 900; letter-spacing: .24em; }
    h1 { margin: 10px 0 6px; font: 600 clamp(34px, 7vw, 56px)/1 Georgia, "STSong", serif; }
    .subtitle { margin: 0; color: #97909e; font-size: 14px; }
    .clock { position: relative; width: min(310px, 72vw); aspect-ratio: 1; margin: 32px auto; display: grid; place-items: center; }
    svg { position: absolute; inset: 0; width: 100%; transform: rotate(-90deg); }
    circle { fill: none; stroke-width: 8; }
    .track { stroke: #342f3a; }
    .progress { stroke: #ff8258; stroke-linecap: round; stroke-dasharray: 603.19; stroke-dashoffset: 0; filter: drop-shadow(0 0 6px rgba(255,130,88,.55)); transition: stroke-dashoffset .25s linear; }
    .time { font: 500 clamp(48px, 12vw, 76px)/1 "Courier New", monospace; letter-spacing: -.07em; font-variant-numeric: tabular-nums; }
    .state { display: block; margin-top: 12px; color: #9f98a7; font-size: 11px; font-weight: 800; letter-spacing: .15em; }
    .settings { display: flex; justify-content: center; gap: 12px; align-items: center; margin-bottom: 24px; }
    .settings label { color: #aba4b1; font-size: 13px; }
    input { width: 88px; padding: 10px 12px; border: 1px solid #48414f; border-radius: 10px; color: #fff8e8; font: 700 15px/1 "Courier New", monospace; background: #18161d; }
    input:focus { outline: 2px solid #ff8258; outline-offset: 2px; }
    .actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; }
    button { min-width: 120px; padding: 13px 20px; border: 1px solid #48414f; border-radius: 999px; color: #d5ced9; font: inherit; font-weight: 800; background: #2b2731; cursor: pointer; }
    button:hover { border-color: #746b7a; }
    .primary { border-color: #ff8258; color: #17151c; background: #ff8258; }
    button:focus-visible { outline: 3px solid white; outline-offset: 3px; }
    @media (prefers-reduced-motion: reduce) { .progress { transition: none; } }
  </style>
</head>
<body>
  <main class="timer-card">
    <span class="eyebrow">FOCUS SESSION</span><h1>Réservez un moment de concentration</h1><p class="subtitle">Réglez les minutes et commencez une période de travail sans interruption.</p>
    <div class="clock" aria-live="polite">
      <svg viewBox="0 0 220 220" aria-hidden="true"><circle class="track" cx="110" cy="110" r="96"></circle><circle id="progress" class="progress" cx="110" cy="110" r="96"></circle></svg>
      <div><div id="time" class="time">05:00</div><span id="state" class="state">Prêt à commencer</span></div>
    </div>
    <div class="settings"><label for="minutes">Minutes de concentration</label><input id="minutes" type="number" min="1" max="180" value="5" inputmode="numeric"></div>
    <div class="actions"><button id="toggle" class="primary" type="button">Commencer</button><button id="reset" type="button">Réinitialiser</button></div>
  </main>
  <script>
    const timeDisplay = document.querySelector('#time');
    const stateDisplay = document.querySelector('#state');
    const progress = document.querySelector('#progress');
    const minutesInput = document.querySelector('#minutes');
    const toggleButton = document.querySelector('#toggle');
    const resetButton = document.querySelector('#reset');
    const circumference = 2 * Math.PI * 96;
    let totalSeconds = 300;
    let remainingSeconds = totalSeconds;
    let timerId = 0;
    let endAt = 0;

    function render() {
      const minutes = Math.floor(remainingSeconds / 60);
      const seconds = remainingSeconds % 60;
      timeDisplay.textContent = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
      progress.style.strokeDashoffset = String(circumference * (1 - remainingSeconds / totalSeconds));
    }
    function stopTimer(message = 'En pause') {
      window.clearInterval(timerId);
      timerId = 0;
      toggleButton.textContent = remainingSeconds === 0 ? 'Recommencer' : 'Continuer';
      stateDisplay.textContent = message;
    }
    function tick() {
      remainingSeconds = Math.max(0, Math.ceil((endAt - Date.now()) / 1000));
      render();
      if (remainingSeconds === 0) stopTimer('Concentration terminée · Bon travail');
    }
    function startTimer() {
      if (remainingSeconds === 0) resetTimer();
      endAt = Date.now() + remainingSeconds * 1000;
      timerId = window.setInterval(tick, 250);
      toggleButton.textContent = 'Pause';
      stateDisplay.textContent = 'Concentration en cours';
    }
    function resetTimer() {
      window.clearInterval(timerId);
      timerId = 0;
      const minutes = Math.min(180, Math.max(1, Number(minutesInput.value) || 5));
      minutesInput.value = String(minutes);
      totalSeconds = minutes * 60;
      remainingSeconds = totalSeconds;
      toggleButton.textContent = 'Commencer';
      stateDisplay.textContent = 'Prêt à commencer';
      render();
    }
    toggleButton.addEventListener('click', () => timerId ? stopTimer() : startTimer());
    resetButton.addEventListener('click', resetTimer);
    minutesInput.addEventListener('change', resetTimer);
    render();
  </script>
</body>
</html>
Commentaires sur la pageVous avez découvert un problème ou avez une suggestion d’amélioration ?
Les fonctions de retour nécessitent l’utilisation de la version en ligne

Veuillez activer JavaScript dans la version en ligne avant de soumettre ; les fonctionnalités locales de l’outil ou du cas ne sont pas affectées.

Aller vers la version en ligne pour envoyer des commentaires