【问题标题】:Javascript countdown Timer will not stop at zero and does not restart at given timeJavascript倒计时计时器不会在零处停止并且不会在给定时间重新启动
【发布时间】:2018-08-19 14:09:20
【问题描述】:

我的用例如下:

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

在 UI 上,它应该直观地显示 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(唤醒时间)就不会重新启动到达。

【问题讨论】:

  • 你为什么要评论clearInterval?现在只需将(hours &lt;= 0 || minutes &lt;= 0 || seconds &lt;= 0) 作为条件。应该有一种更优雅的方式来计算和比较Time 值。
  • 好的,我明白了,您确实想要一个无限滴答声并暂时暂停显示。
  • 您能否编辑您的问题以包含一个可重现您的问题的可运行的 sn-p?这将有助于缩小您的问题。请注意,您是 not allowed 在代码 sn-p 中使用 localStorage
  • @luke-codewalker - 已确认并删除以符合准则 :)
  • @EbonyMaw 你知道你可以在你的问题中直接嵌入可运行代码sn-ps (stackoverflow.blog/2014/09/16/…)吗?我已相应地编辑了您的问题。链接可能会中断,并且在 codepen.io 上我们没有您的问题的上下文。

标签: javascript time countdown


【解决方案1】:

对日期/时间对象之间的差异进行模运算将为您提供日期/时间 下次时钟会显示当天的那个时间。

Modulo 是除法的其余部分。让我们用一个 24 小时的简化示例来说明这一点:

现在是第 2 天的 13:00。

第 3 天的 15:00 有一个闹钟。 到现在的区别是 (15-13 + (3-2)*24) = 26。 26 模 24 的结果是 2,认为它是 26 / 24 = 1 rest 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() 并根据当前时间计算下一个完整秒的触发时间,但是, 回调可能会提前几毫秒触发。因此我们不能依赖Date 对象的getSeconds()。 由于这个事实,我们需要实现一个近似值,将舍入到整秒或 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>

【讨论】:

  • 如果有人可以改进我的英语表达,请随时编辑!
【解决方案2】:

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

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;

【讨论】:

    【解决方案3】:
    $(document).ready(function () {
    
      function countdown() {
         var bt = "23:00",  // 11:00 PM
             wt = "08:00";  // 08:00 AM
    
    
        var today = new Date(),
            dd = today.getDate(),
            mm = today.getMonth()+1,
            yyyy = today.getFullYear();
    
        var startTime = new Date(mm + '/' + dd + '/' + yyyy + ' ' + wt),
            endTime = new Date(mm + '/' + dd + '/' + yyyy + ' ' + bt);
    
        setInterval(function() {
           var now = new Date();
           var nowdd = today.getDate();
           var nowTime = now.getTime();
          if(dd !== nowdd) {
            dd = nowdd;
            startTime = new Date(dd + '/' + mm + '/' + yyyy + ' wt');
            endTime = new Date(dd + '/' + mm + '/' + yyyy + ' bt');
          }
    
          if(nowTime > startTime && nowTime < endTime) {
             // Find the distance between now and the count down date
                var distance = endTime - nowTime;
    
                // Time calculations for days, hours, minutes and seconds
                var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
                    minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)),
                    seconds = Math.floor((distance % (1000 * 60)) / 1000);
                $("#countDown").val(hours + ":" + minutes + ":" + seconds);
             } else {
               $("#countDown").val("00:00:00");
             }
        }, 1000);
      }
      countdown();
    });
    

    开启CodePen

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-29
      • 2017-03-31
      • 2014-09-05
      相关资源
      最近更新 更多