带有动画轮子的计时器和锁定屏幕的错误 [英] Timer with animated wheels and errors with the screen locked

查看:63
本文介绍了带有动画轮子的计时器和锁定屏幕的错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用车轮动画创建间隔计时器.计时器应该以这种方式工作:如果有人只键入工作中的秒数,那么秒数将无限次地递减.如果有人输入工作中的秒数并中断,那么秒数将无限地递减.如果您输入所有字段,计时器将与顶部的计时器相同.

I'm creating an interval timer with wheel animation. The timer is supposed to work this way: If somebody only types the seconds in the work - the seconds count down, infinite times. If someone types the seconds in work and break, the seconds count down infinitely. If you enter all the fields, the timer will work the same way as at the top.

我有几个问题.为什么在手机屏幕锁定时此计时器无法正常工作,有时秒数倒计时严重,或倒数至负数.如何使这些最小的圆圈变小,以便每当倒数从输入的重复编号的开始到结束开始时,该行都减小.这段代码完全正确吗?请帮助我,我不知道该怎么做.

I have a few questions. Why does this timer not work properly when the phone screen is locked, sometimes the seconds count down badly, or count down to minus numbers. How can I make these smallest circle so that this line decreases every time the countdown starts from the beginning to the end of the entered repetition numbers. Is this code at all correct?Please help me, I have no idea how to do this anymore.

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的一个实例来操作整个计时器.当计数器过早结束(例如,通过再次单击开始")时,您的代码还难以将计时器重置为其初始状态.

Modification

Your code has a lot of different instances of setInterval. Personally, I think that this makes it a little hard to work on. Objectively, this can cause some issues related to the execution timing. So, I've taken the liberty to modify your code a little so that it only uses one instance of setTimeout to operate the whole timer thing. Your code also struggles with resetting the timer to its initial state when the counter is ended prematurely (e.g. by clicking start again).

该解决方案的工作原理如下:

The solution works as follows:

  • 如果未指定重复值,则计数器将无限期重复
  • 在您的示例可运行代码段之后,我做到了这一点,以使工作时间休息时间的第一刻具有1s的延迟
  • 该解决方案仅使用一个setTimeout实例,该实例可用于清除前一个计数器
  • 当计数器过早结束时,代码会将计时器重置为其初始状态
  • 代码使用值-1来检测何时未插入 break值 repeat值或将其设置为0.然后将此-1值用于执行您想要的操作,即不运行休息时间计数器并无限期重复.此-1值还确定了圆圈视觉效果的初始状态.
  • 设置超时的countDown函数如下所示:
  • If the repeat value is not specified, the counter repeats indefinitely
  • Following your example runnable snippet, I've made it so that the first tick of the work time and break time have a delay of 1s
  • The solution only uses one setTimeout instance, which can be used to clear the previous counter
  • The code resets the timer to its initial state when the counter is ended prematurely
  • The code uses the value -1 to detect when the break value and repeat value are not inserted or are 0. This -1 value is then used to do what you wanted, i.e. not running the break time counter and repeating indefinitely. This -1 value also determines the initial state of the circles' visuals.
  • The countDown function which sets the timeout works like so:
  1. 它总是检查workValue是否为0.如果不是,请设置一个新的超时,在1s之后使用更新的workValue(即当前workValue - 1)调用countDown函数.它还会更新文本和视觉效果.
  2. workValue为0时,该运行中断时间计数器了,但是只有其值不为0时,该逻辑才起作用.workValue(使用更新的breakValue [即当前breakValue - 1]每1秒设置一个超时时间
  3. breakValue为0时,它将检查repeatValue.如果repeatValue-1(表示无限重复),则将计数器重置为其初始状态,然后再次使用初始值调用新的超时.
  4. 但是,如果repeatValue不是-1且确实已指定(且大于0),则将计数器重置为其几乎初始状态,区别是现在已更新为repeatValue - 1.这将一直持续到repeatValue为0.
  5. repeatValue为0时,更新视觉效果并将文本设置为-.另外,停止计时器.
  1. It always checks if workValue is 0 or not. If not, set a new timeout calling the countDown function after 1s with the updated workValue (i.e. current workValue - 1). It also updates the text and the visual.
  2. When workValue is 0, it's time to run the break time counter, but only if its value is not 0. The logic works like workValue (setting a timeout every 1s with the updated breakValue [i.e. current breakValue - 1])
  3. When breakValue is 0, it will check for repeatValue. If repeatValue is -1 (meaning repeating indefinitely), reset the counter to its initial state and call a new timeout with the initial value again.
  4. However, if the repeatValue is not -1 and is indeed specified (and larger than 0), reset the counter to its almost-initial state, the difference being that the repeat value is now updated to repeatValue - 1. This will continue until repeatValue is 0.
  5. When repeatValue is 0, update the visuals and set the text to -. Also, stop the timer.

这是可行的解决方案.请尝试运行它;-)

Here's the working solution. Do try running it ;-)

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,并且必须对代码进行大量修改.

As requested, here's the solution that still causes the timer to work properly even when it is run in the background in a mobile device (e.g. when the phone is locked). Instead of using setTimeout, you can use setInterval for this and you'd have to modify the code by quite a lot.

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>

这篇关于带有动画轮子的计时器和锁定屏幕的错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆