我有这个 JS 倒计时器,但它不能按我想要的方式工作。我需要一个正常的时间,例如 60 秒。绿色进度条应从左向右移动。当剩余时间为 10 秒时,进度条应变为黄色,当剩余时间为 5 秒时,进度条应变为红色。时间结束后,它将保持红色。
紧要关头,到了23秒的时候,进度条就停在这个地方了。
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 = 20;
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>
`;
startTimer();
function onTimesUp() {
clearInterval(timerInterval);
}
function startTimer() {
timerInterval = setInterval(() => {
timePassed = 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;
if (timeLeft <= alert.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(warning.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(alert.color);
} else if (timeLeft <= warning.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(info.color);
document
.getElementById("base-timer-path-remaining")
.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);
}
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>
这是一个示例,说明我如何开始制作不是圆形的进度条,并在不同的阈值上变成不同的颜色。
首先,我制作一个用于时间显示和进度条的容器。然后我为进度条容器创建一个子级,这是实际的进度。然后,我使用js更新进度的css变量,该变量用于更新变换scaleX。我使用scaleX而不是width的原因是它更适合过渡并防止回流dom。
然后剩下的就是在js中使用一些异步魔法,每秒循环一次,检查进度以查看颜色是否需要更新,并更新进度条的比例。
如果您想要进度条更平滑的过渡,您可以进行过渡以缩放计时器的长度,但是如果您打算将计时器用于游戏或其他内容,那么它会给您带来不太精细的控制。另外,这种方法开启了暂停、改变时间的能力,我只是喜欢滴答作响而不是逐渐减少的美感。就好像游戏是 1 秒间隔更新而不是连续更新。不过,可以根据您的意图以任何一种方式编写。
希望这个演示有帮助!如果您还有其他疑问,请随时询问。
我利用 css 变量来保存我的描述性颜色
const get = query => document.querySelector(query);
const wait = s => new Promise(r => setTimeout(r, s * 1000));
const display = get(".timer .display");
const progress = get(".timer .progress");
const WARN_THRESHOLD = .4;
const DANGER_THRESHOLD = .2;
const INIT_TIME = 15;
let timeRemaining = INIT_TIME;
(async () => {
while (timeRemaining > 0) {
timeRemaining--;
display.innerText = `${timeRemaining}s`;
const newProgress = timeRemaining / INIT_TIME;
progress.style.setProperty("--progress", newProgress);
if (newProgress > WARN_THRESHOLD) {
progress.style.setProperty("--color", "var(--safe)");
} else if (newProgress > DANGER_THRESHOLD) {
progress.style.setProperty("--color", "var(--warn)");
} else {
progress.style.setProperty("--color", "var(--danger)");
}
await wait(1);
}
})();
/* CENTER TIMER */
.timer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* FLOW TIMER CONTENT */
.timer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
width: 200px;
border: 5px solid lightblue;
}
.timer .display {
}
.timer .progress-container {
width: 100%;
border: 1px solid black;
--height: 20px;
height: var(--height);
}
:root {
--safe: lightgreen;
--warn: yellow;
--danger: red;
}
.timer .progress {
--color: var(--safe);
--progress: 1;
background-color: var(--color);
width: 100%;
height: var(--height);
transform: scaleX(var(--progress));
transform-origin: left;
transition: transform 0.1s, color 0.1s;
}
<div class="timer">
<div class="display"></div>
<div class="progress-container">
<div class="progress">
</div>
</div>
</div>