我正在创建一个带有轮子动画的间隔计时器。计时器应该这样工作:如果有人只在工作中输入秒数 - 秒数会倒计时,无限次。如果有人在工作和休息时输入秒数,则秒数会无限倒计时。如果您输入所有字段,计时器的工作方式将与顶部相同。
我有几个问题。为什么这个定时器在手机屏幕锁定时无法正常工作,有时秒倒数不好,或者倒数为负数。我怎样才能制作这些最小的圆圈,以便每次从输入的重复数字的开始到结束倒计时时,这条线都会减少。这段代码完全正确吗?请帮助我,我不知道该怎么做。
var circle1 = document.querySelectorAll('circle')[0];
var circle2 = document.querySelectorAll('circle')[1];
var circle3 = document.querySelectorAll('circle')[2];
var circumference1 = circle1.getTotalLength();
var circumference2 = circle2.getTotalLength();
var circumference3 = circle3.getTotalLength();
circle1.style.strokeDasharray = circumference1;
circle2.style.strokeDasharray = circumference2;
circle3.style.strokeDasharray = circumference3;
function setProgress1(percent) {
var offset = circumference1 - percent / 100 * circumference1;
circle1.style.strokeDashoffset = offset;
}
function setProgress2(percent) {
var offset = circumference2 - percent / 100 * circumference2;
circle2.style.strokeDashoffset = offset;
}
function setProgress3(percent) {
var offset = circumference3 - percent / 100 * circumference3;
circle3.style.strokeDashoffset = offset;
}
document.getElementById('btn').addEventListener('click',function(){
var workValue = Math.ceil(document.getElementById('work-seconds').value),
breakValue = Math.ceil(document.getElementById('break-seconds').value),
repeatValue = Math.ceil(document.getElementById('repeat').value),
showSec = document.querySelector('text');
clearInterval(countDownI);
if(repeatValue > 0){
breakValue > 0 ? setTimeout(end,((workValue + breakValue + 2) * repeatValue) * 1000) : setTimeout(end,((workValue + breakValue + 1) * repeatValue) * 1000);
}
if(breakValue > 0){
var countDownI = setInterval(countDown,(workValue + breakValue + 2) * 1000);
} else {
var countDownI = setInterval(countDown,(workValue + breakValue + 1) * 1000);
}
function end(){
clearInterval(countDownI);
showSec.innerHTML = '–';
}
countDown();
function countDown(){
var workSec = new Date().getTime() + workValue * 1000;
countWorkSec();
var workI = setInterval(countWorkSec,1000);
function countWorkSec(){
var countSec = Math.ceil((workSec - new Date().getTime()) / 1000);
countSec < 10 ? showSec.textContent = "0" + countSec : showSec.textContent = countSec;
showSec.style.fontSize = 'calc(74px /' + showSec.textContent.length + ')';
setProgress1(countSec * 100 / workValue);
if(countSec <= 0){
clearInterval(workI);
setTimeout(function(){
setProgress1(100);
},1000);
};
};
if(breakValue > 0){
setTimeout(function(){
var breakSec = new Date().getTime() + breakValue * 1000;
countBreakSec();
var breakI = setInterval(countBreakSec,1000);
function countBreakSec(){
var countSec = Math.ceil((breakSec - new Date().getTime()) / 1000);
countSec < 10 ? showSec.textContent = "0" + countSec : showSec.textContent = countSec;
showSec.style.fontSize = 'calc(74px /' + showSec.textContent.length + ')';
setProgress2(countSec * 100 / breakValue);
if(countSec <= 0){
clearInterval(breakI);
setTimeout(function(){
setProgress2(100);
},1000);
};
};
},(workValue + 1) * 1000);
};
};
});
svg circle {
fill: none;
stroke: #f90;
stroke-width: 21;
stroke-linecap: round;
transition: 1s;
transform: rotate(-90deg);
transform-origin: center center;
}
<svg width="233" height="233">
<circle cx="50%" cy="50% " r="calc(50% - 10.5px)"/>
<circle cx="50%" cy="50% " r="calc(50% - 34.5px)"/>
<circle cx="50%" cy="50% " r="calc(50% - 58.5px)"/>
<text x="50%" y="50%" text-anchor="middle" alignment-baseline="middle"></text>
</svg>
<p>Work</p>
<input type="number" id="work-seconds" placeholder="seconds" min="0">
<br>
<p>Break</p>
<input type="number" id="break-seconds" placeholder="break" min="0">
<br>
<p>Repeat</p>
<input type="number" id="repeat" placeholder="repeat" min="0">
<br>
<br>
<button id="btn">START</button>
最佳答案
修改
您的代码有许多不同的 setInterval
实例。就我个人而言,我认为这使得工作有点困难。客观上,这可能会导致一些与执行时序相关的问题。因此,我冒昧地稍微修改了您的代码,以便它只使用 setTimeout
的一个实例。操作整个计时器的事情。当计数器过早结束时(例如,通过再次单击“开始”),您的代码还很难将计时器重置为其初始状态。
解决方案
解决方案的工作原理如下:
- 如果未指定重复值,则计数器无限重复
- 按照您的示例可运行代码片段,我已将工作时间和休息时间的第一个刻度设置为延迟1秒
- 该解决方案仅使用一个
setTimeout
实例,可用于清除之前的计数器 - 当计数器提前结束时,代码会将计时器重置为其初始状态
- 代码使用值
-1
检测中断值和重复值何时未插入或为0。此-1
然后使用值执行您想要的操作,即不运行中断时间计数器并无限期地重复。这个-1
值还决定了圆圈视觉效果的初始状态。 countDown
设置超时的函数的工作原理如下:- 它始终检查是否
workValue
是否为 0。如果没有,请设置一个新的超时,调用countDown
1 秒后函数更新为workValue
(即当前workValue - 1
)。它还更新了文本和视觉效果。 - 何时
workValue
为 0,则需要运行中断时间计数器,但仅当其值不为 0。逻辑工作方式类似于workValue
(每 1 秒设置一次超时,更新的breakValue
[即当前breakValue - 1
]) - 何时
breakValue
为 0,它将检查repeatValue
。如果repeatValue
是-1
(意味着无限期地重复),将计数器重置为其初始状态,并再次使用初始值调用新的超时。 - 但是,如果
repeatValue
不是-1
并且确实被指定(并且大于 0),将计数器重置为其几乎初始状态,区别在于重复值现在更新为repeatValue - 1
。这将持续到repeatValue
是 0。 - 何时
repeatValue
为 0,更新视觉效果并将文本设置为-
。另外,停止计时器。
- 它始终检查是否
这是可行的解决方案。请尝试运行它;-)
window.onload = function() {
var circle1 = document.querySelectorAll('circle')[0];
var circle2 = document.querySelectorAll('circle')[1];
var circle3 = document.querySelectorAll('circle')[2];
var circumference1 = circle1.getTotalLength();
var circumference2 = circle2.getTotalLength();
var circumference3 = circle3.getTotalLength();
circle1.style.strokeDasharray = circumference1;
circle2.style.strokeDasharray = circumference2;
circle3.style.strokeDasharray = circumference3;
function setProgress1(percent) {
var offset = circumference1 - percent / 100 * circumference1;
circle1.style.strokeDashoffset = offset;
}
function setProgress2(percent) {
var offset = circumference2 - percent / 100 * circumference2;
circle2.style.strokeDashoffset = offset;
}
function setProgress3(percent) {
var offset = circumference3 - percent / 100 * circumference3;
circle3.style.strokeDashoffset = offset;
}
var timeout
document.getElementById('btn').addEventListener('click', function() {
var initialWorkValue = Math.ceil(document.getElementById('work-seconds').value),
initialBreakValue = Math.ceil(document.getElementById('break-seconds').value) || -1,
initialRepeatValue = Math.ceil(document.getElementById('repeat').value) || -1,
showSec = document.querySelector('text'),
workTime = initialWorkValue * 1000,
breakTime = initialBreakValue * 1000,
workAndBreakTime = workTime + breakTime,
totalTime = initialRepeatValue * workAndBreakTime,
initialBreakProgress = initialBreakValue === -1 ? 0 : 100,
initialRepeatProgress = initialRepeatValue === -1 ? 0 : 100
// Reset timer
clearTimeout(timeout)
setProgress1(100)
setProgress2(initialBreakProgress)
setProgress3(initialRepeatProgress)
countDown(initialRepeatValue, initialWorkValue, initialBreakValue)
function countDown(repeatValue, workValue, breakValue) {
if (workValue >= 0) {
setProgress1(workValue * 100 / initialWorkValue)
showSec.textContent = workValue
timeout = setTimeout(countDown, 1000, repeatValue, workValue -= 1, breakValue)
} else if (breakValue >= 0) {
setProgress2(breakValue * 100 / initialBreakValue)
showSec.textContent = breakValue
timeout = setTimeout(countDown, 1000, repeatValue, workValue, breakValue -= 1)
} else if (repeatValue === -1) {
setProgress1(100)
setProgress2(initialBreakProgress)
countDown(repeatValue, initialWorkValue, initialBreakValue)
} else if ((repeatValue - 1) > 0) {
setProgress1(100)
setProgress2(initialBreakProgress)
setProgress3((repeatValue - 1) * 100 / initialRepeatValue)
countDown(repeatValue -= 1, initialWorkValue, initialBreakValue)
} else {
clearTimeout(null)
setProgress3(0)
showSec.innerHTML = '–'
}
showSec.style.fontSize = `calc(30px / ${showSec.textContent.length})`
}
})
}
svg circle {
fill: none;
stroke: #f90;
stroke-width: 21;
stroke-linecap: round;
transition: 1s;
transform: rotate(-90deg);
transform-origin: center center;
}
<svg width="233" height="233">
<circle cx="50%" cy="50% " r="calc(50% - 10.5px)"/>
<circle cx="50%" cy="50% " r="calc(50% - 34.5px)"/>
<circle cx="50%" cy="50% " r="calc(50% - 58.5px)"/>
<text x="50%" y="50%" text-anchor="middle" alignment-baseline="middle"></text>
</svg>
<p>Work</p>
<input type="number" id="work-seconds" placeholder="seconds" min="0">
<br>
<p>Break</p>
<input type="number" id="break-seconds" placeholder="break" min="0">
<br>
<p>Repeat</p>
<input type="number" id="repeat" placeholder="repeat" min="0">
<br>
<br>
<button id="btn">START</button>
根据要求,以下解决方案即使在移动设备的后台运行(例如,当手机锁定时),仍然可以使计时器正常工作。而不是使用 setTimeout
,您可以使用setInterval
为此,您必须对代码进行大量修改。
window.onload = function() {
const button = document.querySelector('#btn')
const circle1 = document.querySelectorAll('circle')[0]
const circle2 = document.querySelectorAll('circle')[1]
const circle3 = document.querySelectorAll('circle')[2]
const showSec = document.querySelector('text')
const circumference1 = circle1.getTotalLength()
const circumference2 = circle2.getTotalLength()
const circumference3 = circle3.getTotalLength()
circle1.style.strokeDasharray = circumference1
circle2.style.strokeDasharray = circumference2
circle3.style.strokeDasharray = circumference3
function setProgress1(percent) {
let offset = circumference1 - percent / 100 * circumference1
circle1.style.strokeDashoffset = offset
}
function setProgress2(percent) {
let offset = circumference2 - percent / 100 * circumference2
circle2.style.strokeDashoffset = offset
}
function setProgress3(percent) {
let offset = circumference3 - percent / 100 * circumference3
circle3.style.strokeDashoffset = offset
}
let interval
btn.addEventListener('click', function() {
let counterValues = new (function() {
this.workTimeStartDelay = 1000
this.workTime = Math.ceil(document.getElementById('work-seconds').value) * 1000
this.breakTimeStartDelay = 1000
this.breakTime = Math.ceil(document.getElementById('break-seconds').value) * 1000 || -1 // Checking for 0
this.repeatValue = Math.ceil(document.getElementById('repeat').value) || -1 // Checking for 0
this.startTime = Date.now()
})()
// Clearing interval and restart the timer
clearInterval(interval)
setProgress1(100)
setProgress2(counterValues.breakTime === -1 ? 0 : 100)
setProgress3(counterValues.repeatValue === -1 ? 0 : 100)
showSec.textContent = 'Go'
interval = setInterval(countDown, 1000, {...counterValues})
showSec.style.fontSize = `calc(30px / ${showSec.textContent.length})`
// Counting down that works even when mobile phone is locked
function countDown(values) {
let nowTime = Date.now()
let elapsedTimeSinceStart = nowTime - values.startTime
let repetitionTime = values.workTimeStartDelay + values.workTime
if (values.breakTime !== -1) repetitionTime += values.breakTimeStartDelay + values.breakTime
let currRepElapsedTime = elapsedTimeSinceStart % repetitionTime
// Timer should or should have stopped
// Don't continue after this if when true
let totalTime = values.repeatValue === -1 ? -1 : values.repeatValue * repetitionTime
if (totalTime !== -1 && totalTime <= elapsedTimeSinceStart) {
setProgress1(0)
setProgress2(0)
setProgress3(0)
showSec.innerHTML = '–'
clearInterval(interval)
return
}
let counterState = 0 // 0 = still on workTimeStartDelay, 1 = workTime, 2 = breakTimeStartDelay, 3 = breakTime
// Determine which counter the timer is counting down
let counterKeys = Object.keys(values).splice(0, 4)
for (let key of counterKeys) {
if (values[key] !== -1 && currRepElapsedTime >= values[key]) {
currRepElapsedTime -= values[key]
counterState += 1
} else break
}
// Apply different logic for different state the counter is at
if (counterState === 0) {
setProgress1(0)
setProgress2(0)
showSec.textContent = 0
} else if (counterState === 1) {
let currentTimeText = Math.floor(values.workTime / 1000) - Math.floor((elapsedTimeSinceStart % repetitionTime - values.workTimeStartDelay) / 1000)
setProgress1(currentTimeText / Math.floor(values.workTime / 1000) * 100)
setProgress2(counterValues.breakTime === -1 ? 0 : 100)
showSec.textContent = currentTimeText
} else if (counterState === 2) {
setProgress1(0)
setProgress2(100)
showSec.textContent = 0
} else if (counterState === 3) {
let currentTimeText = Math.floor(values.workTime / 1000) - Math.floor((elapsedTimeSinceStart % repetitionTime - values.workTimeStartDelay - values.workTime - values.breakTimeStartDelay) / 1000)
setProgress1(0)
setProgress2(currentTimeText / Math.floor(values.breakTime / 1000) * 100)
showSec.textContent = currentTimeText
}
if (values.repeatValue !== -1) {
let repeatedBy = Math.floor(elapsedTimeSinceStart / repetitionTime)
console.log(repeatedBy)
let repeatPercent = 100 - (repeatedBy / values.repeatValue) * 100
setProgress3(repeatPercent)
} else {
setProgress3(0)
}
showSec.style.fontSize = `calc(30px / ${showSec.textContent.length})`
}
})
}
svg circle {
fill: none;
stroke: #f90;
stroke-width: 21;
stroke-linecap: round;
transition: 1s;
transform: rotate(-90deg);
transform-origin: center center;
}
<svg width="233" height="233">
<circle cx="50%" cy="50% " r="calc(50% - 10.5px)"/>
<circle cx="50%" cy="50% " r="calc(50% - 34.5px)"/>
<circle cx="50%" cy="50% " r="calc(50% - 58.5px)"/>
<text x="50%" y="50%" text-anchor="middle" alignment-baseline="middle"></text>
</svg>
<p>Work</p>
<input type="number" id="work-seconds" placeholder="seconds" min="0">
<br>
<p>Break</p>
<input type="number" id="break-seconds" placeholder="break" min="0">
<br>
<p>Repeat</p>
<input type="number" id="repeat" placeholder="repeat" min="0">
<br>
<br>
<button id="btn">START</button>
<div class="visibilityChanged"></div>
关于javascript - 带有动画轮子的计时器和屏幕锁定时出现的错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61500333/