小应用与小游戏

活动倒计时

可设置分钟数并支持暂停、继续与重置的倒计时器。

HTMLCSSJavaScript计时器
启用 JavaScript 后可编辑、运行和保存此案例。固定源码如下:
<!doctype html>
<html lang="zh-Hans" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>活动倒计时</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>留一段专注时间</h1><p class="subtitle">设定分钟,开始一段不被打扰的工作。</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">准备开始</span></div>
    </div>
    <div class="settings"><label for="minutes">专注分钟</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">开始</button><button id="reset" type="button">重置</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 = '已暂停') {
      window.clearInterval(timerId);
      timerId = 0;
      toggleButton.textContent = remainingSeconds === 0 ? '重新开始' : '继续';
      stateDisplay.textContent = message;
    }
    function tick() {
      remainingSeconds = Math.max(0, Math.ceil((endAt - Date.now()) / 1000));
      render();
      if (remainingSeconds === 0) stopTimer('专注完成 · 做得好');
    }
    function startTimer() {
      if (remainingSeconds === 0) resetTimer();
      endAt = Date.now() + remainingSeconds * 1000;
      timerId = window.setInterval(tick, 250);
      toggleButton.textContent = '暂停';
      stateDisplay.textContent = '专注进行中';
    }
    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 = '开始';
      stateDisplay.textContent = '准备开始';
      render();
    }
    toggleButton.addEventListener('click', () => timerId ? stopTimer() : startTimer());
    resetButton.addEventListener('click', resetTimer);
    minutesInput.addEventListener('change', resetTimer);
    render();
  </script>
</body>
</html>
页面反馈发现问题或有改进建议?
反馈功能需要在在线版使用

请在在线版启用 JavaScript 后提交;当前工具或案例的本地功能不受影响。

前往在线版反馈