Модификация
В вашем коде много разных экземпляров setInterval
. Лично я думаю, что это немного усложняет работу. Объективно это может вызвать некоторые проблемы, связанные со сроками исполнения. Итак, я позволил себе немного изменить ваш код, чтобы он использовал только один экземпляр setTimeout
для работы всего таймера. Ваш код также борется с возвратом таймера в его начальное состояние, когда счетчик преждевременно завершает работу (например, путем повторного нажатия кнопки запуска).
Решение
Решение работает следующим образом:
- Если значение повторения не указано, счетчик повторяется бесконечно
- Следуя вашему примеру запускаемого фрагмента, я сделал так, чтобы первый тик рабочего времени и время останова имеет задержку в 1 с
- В решении используется только один экземпляр
setTimeout
, который можно использовать для очистки предыдущего счетчика - Код сбрасывает таймер до исходного состояния, когда счетчик преждевременно завершается
- Код использует значение
-1
для определения того, когда значение разрыва и повторное значение не вставлены или равны 0. Это значение -1
затем используется, чтобы делать то, что вы хотели, т.е. не запускать счетчик break time и повторяться бесконечно. Это значение -1
также определяет начальное состояние визуальных элементов окружностей. - Функция
countDown
, которая устанавливает время ожидания, работает следующим образом: - Всегда проверяет, равно ли
workValue
0 или нет. Если нет, установите новый тайм-аут, вызывая функцию countDown
через 1 с с обновленным workValue
(то есть текущим workValue - 1
). Он также обновляет текст и изображение. - Когда
workValue
равен 0, пора запустить счетчик break time , но только если его значение не равно 0. Лог c работает как 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>