Таймер с анимированными колесами и ошибками при заблокированном экране - PullRequest
2 голосов
/ 29 апреля 2020

Я создаю интервальный таймер с колесной анимацией. Таймер должен работать следующим образом: если кто-то набирает только секунды в работе - секунды отсчитываются бесконечно. Если кто-то набирает секунды в работе и ломается, секунды отсчитывают бесконечно. Если вы введете все поля, таймер будет работать так же, как и наверху.

У меня есть несколько вопросов. Почему этот таймер не работает должным образом, когда экран телефона заблокирован, иногда секунды плохо отсчитываются или отсчитывают до минус числа. Как я могу сделать эти наименьший круг, чтобы эта линия уменьшалась каждый раз, когда начинается обратный отсчет от начала до конца введенных чисел повторения. Правильно ли этот код? Пожалуйста, помогите мне, я понятия не имею, как это сделать.

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>

1 Ответ

2 голосов
/ 05 мая 2020

Модификация

В вашем коде много разных экземпляров setInterval. Лично я думаю, что это немного усложняет работу. Объективно это может вызвать некоторые проблемы, связанные со сроками исполнения. Итак, я позволил себе немного изменить ваш код, чтобы он использовал только один экземпляр setTimeout для работы всего таймера. Ваш код также борется с возвратом таймера в его начальное состояние, когда счетчик преждевременно завершает работу (например, путем повторного нажатия кнопки запуска).


Решение

Решение работает следующим образом:

  • Если значение повторения не указано, счетчик повторяется бесконечно
  • Следуя вашему примеру запускаемого фрагмента, я сделал так, чтобы первый тик рабочего времени и время останова имеет задержку в 1 с
  • В решении используется только один экземпляр setTimeout, который можно использовать для очистки предыдущего счетчика
  • Код сбрасывает таймер до исходного состояния, когда счетчик преждевременно завершается
  • Код использует значение -1 для определения того, когда значение разрыва и повторное значение не вставлены или равны 0. Это значение -1 затем используется, чтобы делать то, что вы хотели, т.е. не запускать счетчик break time и повторяться бесконечно. Это значение -1 также определяет начальное состояние визуальных элементов окружностей.
  • Функция countDown, которая устанавливает время ожидания, работает следующим образом:
    1. Всегда проверяет, равно ли workValue 0 или нет. Если нет, установите новый тайм-аут, вызывая функцию countDown через 1 с с обновленным workValue (то есть текущим workValue - 1). Он также обновляет текст и изображение.
    2. Когда workValue равен 0, пора запустить счетчик break time , но только если его значение не равно 0. Лог c работает как workValue (установка таймаута каждые 1 с обновленным breakValue [то есть текущим breakValue - 1])
    3. Когда breakValue равен 0, он проверит на repeatValue. Если repeatValue равно -1 (что означает бесконечное повторение), сбросьте счетчик в исходное состояние и снова вызовите новый тайм-аут с начальным значением.
    4. Однако, если repeatValue не -1 и действительно указывается (и больше 0), сбрасывает счетчик в его почти начальное состояние, с отличием в том, что значение повторения теперь обновляется до repeatValue - 1. Это будет продолжаться до тех пор, пока repeatValue не станет 0.
    5. Когда 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 = '&ndash;'
      }

      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 = '&ndash;'
        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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...