Javascript倒数计时器不会在零停止并且不会在给定时间重新启动

时间:2018-08-19 14:09:20

标签: javascript time countdown

我的用例如下:

我需要倒数时钟查看本地机器时间,确定到就寝时间还剩下多少时间,以及 停止 bt(就寝时间)。

在用户界面上,它应可视地显示 00:00:00

但是,只要本地计算机时间与wt(唤醒时间)相同,它就应重新开始倒数计时,直到bt(就寝时间)为止

这应该重复一遍又一遍。

另一个警告是该应用程序可能未运行(即浏览器可能已关闭),并且脚本可能无法满足以下 if 条件:

if (hours === 0 && minutes === 0 && seconds === 0)

我将如何缓解这种情况?

我编写了以下代码:

    $(document).ready(function () {
    
        var bt = "23:00";
        var dat = "10:00";
        var wt = "08:00";
    
        console.log('Bed Time:' + bt);
        console.log('Daily Available time' + dat);
        console.log('Wake up time:' + wt);
    
        placeHolderDate = "Aug 18, 2018 " + bt;
        var countDownDate = new Date(placeHolderDate).getTime();
    
    
        var countDownHourMin = (wt.split(":"));
    
    
    // Update the count down every 1 second
        var x = setInterval(function () {
    
            // Get todays date and time
            var now = new Date().getTime();
    
            // Find the distance between now and the count down date
            var distance = countDownDate - now;
    
            // Time calculations for days, hours, minutes and seconds
            var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
            var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
            var seconds = Math.floor((distance % (1000 * 60)) / 1000);
    
            $("#countDown").val(hours + "h " + minutes + "m " + seconds + "s ");
    
            // If the countdown is over, write some text
            if (hours === 0 && minutes === 0 && seconds === 0) {
                //clearInterval(x);
                $("#countDown").val("00:00:00");
            }
    
            if (hours < 0 || minutes < 0 || seconds < 0) {
               // clearInterval(x);
                $("#countDown").val("00:00:00");
            }
    
            console.log(hours + "h " + minutes + "m " + seconds + "s ");
    
        }, 1000);
    
    
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id="countDown"></p>

该代码似乎可以正常工作,但是仍然存在一些问题。倒数计时会变成负数(只要我可以简单地利用它来实现我的功能,就可以了),并且一旦达到00:00:00wt(唤醒,时间)。

3 个答案:

答案 0 :(得分:1)

对日期/时间对象之间的差异进行模运算将在以下情况下为您提供日期/时间: 时钟会显示下一天的时间。

模数是除法的其余部分。让我们用一个24小时的简化示例进行说明:

第二天是13:00。

第3天的15:00h发出一个警报。 到现在的差异是(15-13 +(3-2)* 24)= 26。 26取模24的结果是2,可以认为是26/24 = 1 休息2

现在,我们在第1天的12:00有了一些警报日期。 到现在的差异是(12-13 +(1-2)* 24)= -25。 -25以24为模的结果是23,因为24的下一个较低的倍数是-48,而-25-(-48)是23。

不幸的是,JavaScript不支持负数的真模运算 开箱即用的数字。 %运算符做类似的事情,它导致无符号值的模。但是,您可以轻松实现自己的真正的取模方法:

  (dividend % divisor) + divisor) % divisor

实际上,我们不是以小时为单位,而是以毫秒为单位进行计算,因此我们将每天的毫秒数作为除数。 因此,将两个日期以每天的模数毫秒为单位的差将为您提供毫秒数,直到时钟显示 Date对象中包含的时间分量。 我们将此添加到当前时间并获得下一个闹钟时间。这样,我们可以比较Date对象并计算 与当前时间的差异。

此外,浏览器中的日期存在一些问题。计时器功能无法正常工作。 您可能会遇到setInterval()的问题。我在Firefox上进行了测试,并且每次毫秒后都会触发inverval。 这很快就会累积到几秒钟和几分钟。

作为解决方法,我们可以使用setTimeout()并根据当前时间计算下一秒的触发时间。 回调可能会提前几毫秒触发。因此,我们不能依赖getSeconds()对象的Date。 因此,我们需要实现一个近似值,该近似值将舍入为整秒或1/100秒。

我们可以扩展Date对象的原型以提高可用性。

$(() =>
{
  const
    millisecondsPerDay = 1000*60*60*24,
    milliSecTolerance  = 0,        // timer functions in browser do not work exactly
    clockPrecision     = 10,       // timer functions in browser do not work exactly, round to 10 millisec.
    emptyTimeString    = new Date().toLocaleTimeString().replace(/\d/g, '-')   // e.g. '--:--:--';
  ;


  // Since JavaScript % operator does not work propperly on neg. numbers, we want a true Modulo operation.
  // 23 mod 10 = 3 (correct); -23 mod 10 = 7   (means 7 more than -30, %-op gives 3)
  // We could do that in a Number prototype method:
  Object.defineProperties(Number.prototype,
  {
    mod : { value: function(n) { return ((this%n)+n)%n; } }
  });


  function lowerPrecision(operand, precision)
  {
    if(void 0 === precision)
      precision = clockPrecision;
    let result = Math.round(operand.valueOf() / precision)*precision;
    return Date.prototype.isPrototypeOf(operand) ? new Date(result) : result;
  }

  // Let's extend the Date object to make it more handy
  Object.defineProperties(Date.prototype,
  {
    toUTCTimeHMS       : { value: function() { return this.toUTCString().match(/(.{8}) GMT/)[1];; }},

    setFormattedTime   : { value: function(timeString)
    {
      this.setHours(...timeString.split(/[:.]/));
      return this;  // support chaining
    }},

    getApproximateDate : { value: function(precision)
    {
      if(void 0 === precision)
        precision = clockPrecision;
      return lowerPrecision(this, precision);
    }},

    getApproximateTime : { value: function(precision) { return this.getApproximateDate().getTime(); } },

      // Returns the next date/time when the time component will be reached
    nextDailyTimeDate  : { get  : function()
    {
      let now = Date.getApproxNow();
      return new Date(now + (this-now).mod(millisecondsPerDay));
    }},
  });


  // Timers do not work accurately. The might execute even some milliseconds too early.
  // Let's define a custom functional now-property that gives an approximated value in steps of some milliseconds.
  Object.defineProperties(Date,
  {
    getApproxNow      : { value: (precision) => lowerPrecision(Date.now(), precision) },
    getDateApproxNow  : { value: (precision) => new Date().getApproximateDate(precision) },
  });


  // ===================================================================================


  var
    nextTick,
    alarms = []
  ;


  function Alarm(tr, collection)
  {
    let
      $tr                 = $(tr)            ,
      input               = $tr.find('td>input')[0],
      th                  = $tr.find('th'      )[0],
      tdRemaining         = $tr.find('td'      )[1]
    ;

    Object.defineProperties(this,
    {
      tr        : { get: () => tr          },
      th        : { get: () => th          },
      input     : { get: () => input       },
      remaining : { get: () => tdRemaining },
      collection: { get: () => collection  },
    });

    this.update();
    this.registerEvents();
  }


  // shared prototype doing all the stuff
  Alarm.prototype = new function()
  {
    Object.defineProperties(this,
    {
      update          : { value: function ()
      {
        this._nextDate = new Date().setFormattedTime(this.input.value).nextDailyTimeDate;
        this.collection.updateDisplay();
      }},

      nextDate        :
      {
        get: function() { return this._nextDate; },
        set: function(value)
        {
          let date;
          switch(Object.getPrototypeOf(value))
          {
            case Date:
              date = value;
              break;
            case String.prototype:
              date = new Date().setFormattedTime(value);
              break;
            case Number.prototype:
              date = new Date(value);
              break;
            default:
              return null;
          }
          this._nextDate = date.nextDailyTimeDate;
          this.input.value = this._nextDate.toLocaleTimeString();
        }
      },
      registerEvents  : { value: function() { $(this.tr).find('input').on('change', (ev) => { this.update(); }); }},
      valueOf         : { value: function() { return this._nextDate } },
      remainingTime   : { get  : function() { return new Date(this._nextDate.getApproximateTime()); } },
      updateDisplay   : { value: function()
      {
        this.remaining.innerText = this === this.collection.nextAlarm
          ? new Date(this.remainingTime - Date.getDateApproxNow()).toUTCTimeHMS()
          : emptyTimeString
        ;

        if(this._nextDate.getApproximateTime() > Date.getDateApproxNow())
          return;

        this.update();
        return true;
      }},
    });
  };


  Object.defineProperties(alarms,
  {
    updateDisplay : { value: function()
    {
      let changed = false;
      do for(let i in this)
        if(changed = this[i].updateDisplay())
          break;
      while(changed); // refresh display of all alarms when any data has changed while processing
    }},

    nextAlarm     : { get  : function()
    {
      return this.length
        ? this.reduce((acc, cur) => cur.nextDate<acc.nextDate ? cur:acc)
        : null
      ;
    }},
  });


  $('#alarm-table tr:nth-child(n+2)').each( (i, tr) =>alarms[i] = new Alarm( tr, alarms ) );


  function onTick()
  {
    alarms.updateDisplay();
  }


  (function tickAtFullSeconds()
  {
    onTick();
    nextTick = setTimeout(tickAtFullSeconds, milliSecTolerance + 1000 - new Date().getMilliseconds());
  })();




  $('#test-button').click((ev) =>
  {
    time = Date.now();
    alarms.forEach(i=>i.nextDate = (time += 5000));
  });
  window.alarms = alarms; //DEBUG global access from browser console
});
tr:nth-child(n+2)>th
{
  text-align: left;
  background-color: silver;
}
td
{
  background-color: lightgray;
}
th
{
  background-color: grey;
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Alarm</title>

  <link rel="stylesheet" href="AlarmTimer.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>



<body>
  <h1> Alarm Timer </h1>
  <h2 id="message"></h2>
  <table id="alarm-table">
    <tr>
      <th>Alarm</th>
      <th>Time</th>
      <th>Remaining</th>
    </tr>
    <tr id="waking-up-time">
      <th>waking-up time</th>
      <td class="time"     ><input type="time" step="1" value="07:15:00"></td>
      <td class="remaining"> --:--:--</td>
    </tr>
    <tr id="noon-hour">
      <th>noon hour</th>
      <td class="time"     ><input type="time" step="1" value="12:00:00"></td>
      <td class="remaining"> --:--:--</td>
    </tr>
    <tr id="bed-time">
      <th>bed time</th>
      <td class="time"     ><input type="time" step="1" value="22:00:00"></td>
      <td class="remaining"> --:--:--</td>
    </tr>
  </table>

  <button id="test-button">set test times</button>

</body>
</html>

答案 1 :(得分:1)

这是我的代码。它开始倒数,然后在秒为零时停止。

import React, { useState, useEffect } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
    const [ minutes, setMinutes ] = useState(0);
    const [ seconds, setSeconds ] = useState(0);

    let userMints = 60;
    let time = userMints * 60;
    const showCountDown = () => {
        const minutes = Math.floor(time / 60);
        let seconds = time % 60;

        if (seconds < 0) {
            setMinutes(0);
            setSeconds(0);
            return;
        } else {
            setMinutes(minutes);
            setSeconds(seconds);
            time--;
        }
        return;
    };

    useEffect(() => {
        callTimerAfterEverySec();
    }, []);

    const callTimerAfterEverySec = () => {
        setInterval(() => {
            showCountDown();
        }, 1000);
    };

    const renderStatus = () => {
        if (seconds === 0 && minutes === 0) {
            return (
                <div>
                    <p>Time Up</p>
                </div>
            );
        } else {
            return (
                <div>
                    <p>
                        {minutes}:{seconds}
                    </p>
                </div>
            );
        }
    };

    return (
        <div className="App">
            {renderStatus()}
            <p>{/* {minutes}:{seconds} */}</p>
        </div>
    );
}

export default App;

答案 2 :(得分:0)

try {
    FileOutputStream fos = new FileOutputStream(fileDir);
    PrintStream ps = new PrintStream(fos);

    ...

} finally {
     fos.close();
     ps.close();
}

CodePen上。