You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为JS环形倒计时器实现开始/暂停/恢复/重置功能

环形倒计时暂停/恢复/重置功能实现

核心实现逻辑:

  • 暂停:清除正在运行的计时定时器,留存当前剩余时间、已流逝时间的变量值,不做重置
  • 恢复:校验当前没有运行中的定时器且剩余时间大于0时,重新启动计时逻辑,从暂停位置继续递减
  • 重置:清除运行中的定时器,将所有时间变量、UI样式(数字显示、环形进度、颜色状态)还原为15秒初始状态
  • 增加重复启动校验:避免多次点击启动按钮生成多个并行定时器,导致计时速度异常

补全后的完整代码

原有CSS和HTML结构不需要调整,替换JS部分即可,补全了三个空函数,同时修复了原代码重复启动、状态重置不彻底的问题:

// Credit: Mateusz Rybczonec
const FULL_DASH_ARRAY = 283;
const WARNING_THRESHOLD = 10;
const ALERT_THRESHOLD = 5;

const COLOR_CODES = {
  info: {
    color: "green"
  },
  warning: {
    color: "orange",
    threshold: WARNING_THRESHOLD
  },
  alert: {
    color: "red",
    threshold: ALERT_THRESHOLD
  }
};

const TIME_LIMIT = 15;
let timePassed = 0;
let timeLeft = TIME_LIMIT;
let timerInterval = null;
let remainingPathColor = COLOR_CODES.info.color;

document.getElementById("app").innerHTML = `
<div class="base-timer">
  <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <g class="base-timer__circle">
      <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
      <path
        id="base-timer-path-remaining"
        stroke-dasharray="283"
        class="base-timer__path-remaining ${remainingPathColor}"
        d="
          M 50, 50
          m -45, 0
          a 45,45 0 1,0 90,0
          a 45,45 0 1,0 -90,0
        "
      ></path>
    </g>
  </svg>
  <span id="base-timer-label" class="base-timer__label">${formatTime(timeLeft)}</span>
</div>
`;

function onTimesUp() {
  clearInterval(timerInterval);
  timerInterval = null;
  alert("Time Up");
}

function startTimer() {
  // 阻止重复生成定时器
  if(timerInterval) return;
  timerInterval = setInterval(() => {
    timePassed += 1;
    timeLeft = TIME_LIMIT - timePassed;
    document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
    setCircleDasharray();
    setRemainingPathColor(timeLeft);

    if (timeLeft === 0) {
      onTimesUp();
    }
  }, 1000);
}

function formatTime(time) {
  const minutes = Math.floor(time / 60);
  let seconds = time % 60;
  if (seconds < 10) {
    seconds = `0${seconds}`;
  }
  return `${minutes}:${seconds}`;
}

function setRemainingPathColor(timeLeft) {
  const { alert, warning, info } = COLOR_CODES;
  const pathEl = document.getElementById("base-timer-path-remaining");
  if (timeLeft <= alert.threshold) {
    pathEl.classList.remove(warning.color, info.color);
    pathEl.classList.add(alert.color);
  } else if (timeLeft <= warning.threshold) {
    pathEl.classList.remove(alert.color, info.color);
    pathEl.classList.add(warning.color);
  }
}

function calculateTimeFraction() {
  const rawTimeFraction = timeLeft / TIME_LIMIT;
  return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);
}

function setCircleDasharray() {
  const circleDasharray = `${(calculateTimeFraction() * FULL_DASH_ARRAY).toFixed(0)} 283`;
  document.getElementById("base-timer-path-remaining").setAttribute("stroke-dasharray", circleDasharray);
}

// 保留原代码拼写,和HTML上的onclick绑定匹配
function starTimer(){
  resetTimer();
  startTimer();
}

function pauseTimer(){
  clearInterval(timerInterval);
  timerInterval = null;
}

function resumeTimer(){
  if(timeLeft > 0 && !timerInterval) {
    startTimer();
  }
}

function resetTimer(){
  // 清除运行中定时器
  clearInterval(timerInterval);
  timerInterval = null;
  // 重置时间变量
  timePassed = 0;
  timeLeft = TIME_LIMIT;
  // 重置数字显示
  document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
  // 重置环形进度
  setCircleDasharray();
  // 重置为初始绿色状态
  const pathEl = document.getElementById("base-timer-path-remaining");
  pathEl.classList.remove(COLOR_CODES.warning.color, COLOR_CODES.alert.color);
  pathEl.classList.add(COLOR_CODES.info.color);
}
body {
  font-family: sans-serif;
  display: grid;
  height: 100vh;
  place-items: center;
}

.base-timer {
  position: relative;
  width: 300px;
  height: 300px;
}

.base-timer__svg {
  transform: scaleX(-1);
}

.base-timer__circle {
  fill: none;
  stroke: none;
}

.base-timer__path-elapsed {
  stroke-width: 7px;
  stroke: grey;
}

.base-timer__path-remaining {
  stroke-width: 7px;
  stroke-linecap: round;
  transform: rotate(90deg);
  transform-origin: center;
  transition: 1s linear all;
  fill-rule: nonzero;
  stroke: currentColor;
}

.base-timer__path-remaining.green {
  color: rgb(65, 184, 131);
}

.base-timer__path-remaining.orange {
  color: orange;
}

.base-timer__path-remaining.red {
  color: red;
}

.base-timer__label {
  position: absolute;
  width: 300px;
  height: 300px;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 48px;
}
<div id="app"></div>

<input type="button" onclick="starTimer()" value="Start"/>
<input type="button" onclick="pauseTimer()" value="Pause"/>
<input type="button" onclick="resumeTimer()" value="Resume"/>
<input type="button" onclick="resetTimer()" value="Reset"/>

功能符合预期

  • 点击Start从15秒开始逐秒递减,10秒变橙色、5秒变红色,到0弹出提示
  • 计时中点击Pause立刻暂停,数字和进度停在当前位置
  • 暂停后点击Resume从暂停位置继续计数,样式同步更新
  • 任意状态点击Reset立刻回到15秒初始状态
  • 重复点击Start不会出现计时跳快问题

内容的提问来源于stack exchange,提问作者Sidhart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:45:17