简体   繁体   English

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

[英]Javascript countdown Timer will not stop at zero and does not restart at given time

My use case is as follows:我的用例如下:

I need my count-down clock to look at the local machine time, determine how much time is left until bed-time, and stop at bt (bed time).我需要我的倒计时时钟来查看本地机器时间,确定离就寝时间还剩多少时间,并在bt (就寝时间)处停止

On the UI, It should visually display 00:00:00 .在 UI 上,它应该在视觉上显示00:00:00

However, as soon as the the local machine time is the same as wt (wake-up time) it should re-start the count-down, up until the bt (bed time).但是,只要本地机器时间与wt (唤醒时间)相同,它就应该重新开始倒计时,直到bt (就寝时间)。

This should repeat, over and over.这应该一遍又一遍地重复。

One other caveat is that the app may not be running (ie browser might be closed) and the script may not be able to honor the following if condition:另外一个需要注意的是,该应用程序可能无法运行(IE浏览器可能会被关闭)和脚本可能无法兑现,如果条件如下:

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

How would I mitigate against this?我将如何减轻这种情况?

I have written the following code:我编写了以下代码:

 $(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>

The code appears to work, yet there are some issues.该代码似乎有效,但存在一些问题。 The countdown goes into negative, (which might be okay, as long as I can simply leverage this for my functionality), and the clock doesn't restart once it has hit 00:00:00 and wt (wake-up time) is reached.倒计时变为负数(这可能没问题,只要我可以简单地利用它来实现我的功能),并且时钟在到达00:00:00wt (唤醒时间)后不会重新启动到达。

A modulo operation on the difference between date/time objects will give you the date/time when the clock will show that time of day next time.对日期/时间对象之间的差异进行模运算将为您提供时钟下次显示该时间的日期/时间。

Modulo is the rest of a division.模数是除法的其余部分。 Let's illustrate this on a simplified example with 24 hours:让我们通过一个 24 小时的简化示例来说明这一点:

It is 13:00h on day 2.现在是第 2 天的 13:00。

One alarm is at 15:00h on day 3. The difference to now is (15-13 + (3-2)*24) = 26. The result of 26 modulo 24 is 2, think of it as 26 / 24 = 1 rest 2 .第 3 天 15:00 有一个闹钟。与现在的差值是 (15-13 + (3-2)*24) = 26。26 模 24 的结果是 2,将其视为 26 / 24 = 1休息 2 .

Now we have some alarm date at 12:00 on day 1. The difference to now is (12-13 + (1-2)*24) = -25.现在我们在第 1 天的 12:00 有一些闹钟日期。与现在的差异是 (12-13 + (1-2)*24) = -25。 The result of -25 modulo 24 is 23 because the next lower multiple of 24 is -48 and -25-(-48) is 23. -25 模 24 的结果是 23,因为 24 的下一个较低倍数是 -48,而 -25-(-48) 是 23。

Unfortunately JavaScript does not support true modulo operations on negative numbers out of the box.不幸的是,JavaScript 不支持开箱即用的对负数的真正模运算。 The % operator does a similar thing, it results in the modulo of the unsigned value. %运算符做类似的事情,它导致无符号值的模。 However, you can easily implement your own true modulo method:但是,您可以轻松实现自己的真模方法:

  (dividend % divisor) + divisor) % divisor

Actually we do not calculate in hours but milliseconds, thus we take the milliseconds per day as divisor.实际上我们计算的不是小时而是毫秒,所以我们以每天的毫秒为除数。 So the difference of two dates modulo milliseconds per day will give you the milliseconds until the clock will show the time component contained in the Date object.因此,每天两个日期模毫秒的差异将为您提供毫秒,直到时钟显示包含在Date对象中的时间组件。 We add this to the current time and get the next alarm time.我们将其添加到当前时间并获得下一个闹钟时间。 This way we can compare the Date object and calculate the difference from the current time.这样我们就可以比较Date对象并计算与当前时间的差异。

Further more there are several problems with dates in browsers.此外,浏览器中的日期还有几个问题。 The timer functions do not work accurately.定时器功能不能准确工作。 You may experience a drift with setInterval() .您可能会在使用setInterval()遇到偏差。 I tested this on Firefox and the inverval is fired each time some milliseconds later.我在 Firefox 上对此进行了测试,每次几毫秒后都会触发 inverval。 This is quickly accumulated into seconds and minutes.这会迅速累积到秒和分钟。

As workaround we can use setTimeout() and calculate the next full second when to fire based on the current time, however, the callback might be fired even some milliseconds too early.作为解决方法,我们可以使用setTimeout()并根据当前时间计算下一个完整秒的触发时间,但是,回调可能会提前几毫秒触发。 Thus we cannot rely on getSeconds() of the Date object.因此我们不能依赖Date对象的getSeconds() Due to that fact we need to implement an approximation which will round to full seconds or 1/100 seconds.由于这个事实,我们需要实现一个近似值,将四舍五入到整秒或 1/100 秒。

We can extend the prototype of the Date object to improve the usability.我们可以扩展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, '-') // eg '--:--:--'; ; // 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>

Here is my code.这是我的代码。 It starts a count down and then stops when seconds are zero.它开始倒计时,然后在秒数为零时停止。

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;
$(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();
});

On CodePen .CodePen 上

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM