如何为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
相关产品推荐
相关产品推荐

