如何在javascript中创建准确的计时器?

如何在javascript中创建准确的计时器?,javascript,time,setinterval,Javascript,Time,Setinterval,我需要创建一个简单但准确的计时器 这是我的代码: var seconds = 0; setInterval(function() { timer.innerHTML = seconds++; }, 1000); 在3600秒之后,它打印大约3500秒 为什么不准确 如何创建准确的计时器 没有比这更准确的了 var seconds = new Date().getTime(), last = seconds, intrvl = setInterval(function() { var

我需要创建一个简单但准确的计时器

这是我的代码:

var seconds = 0;
setInterval(function() {
timer.innerHTML = seconds++;
}, 1000);
在3600秒之后,它打印大约3500秒

  • 为什么不准确

  • 如何创建准确的计时器


    • 没有比这更准确的了

      var seconds = new Date().getTime(), last = seconds,
      
      intrvl = setInterval(function() {
          var now = new Date().getTime();
      
          if(now - last > 5){
              if(confirm("Delay registered, terminate?")){
                  clearInterval(intrvl);
                  return;
              }
          }
      
          last = now;
          timer.innerHTML = now - seconds;
      
      }, 333);
      
      至于为什么不准确,我猜机器正在忙于做其他事情,每次迭代加起来都会稍微慢一点,如您所见

      为什么不准确

      因为您使用的是
      setTimeout()
      setInterval()
      ,它们没有精确性保证。随意的,他们不保持恒定的速度,但是(正如你所观察到的)

      如何创建准确的计时器

      改为使用对象获取(毫秒)精确的当前时间。然后,将逻辑基于当前时间值,而不是计算回调执行的频率

      对于简单的计时器或时钟,请明确跟踪时间差:

      var start = Date.now();
      setInterval(function() {
          var delta = Date.now() - start; // milliseconds elapsed since start
          …
          output(Math.floor(delta / 1000)); // in seconds
          // alternatively just show wall clock time:
          output(new Date().toUTCString());
      }, 1000); // update about every second
      
      现在,这就有了一个问题,即可能会跳转值。当间隔延迟一点并在
      990
      1993
      2996
      3999
      5002
      毫秒后执行回调时,您将看到第二次计数
      0
      1
      2
      3
      5
      (!)。因此,建议更频繁地更新,比如每100毫秒更新一次,以避免这种跳跃

      然而,有时您确实需要一个稳定的时间间隔来执行回调而不会出现漂移。这需要一个更有利的策略(和代码),尽管它回报不错(并且记录的超时更少)。这些被称为自调整定时器。这里,与预期间隔相比,每个重复超时的精确延迟适用于实际经过的时间:

      var interval = 1000; // ms
      var expected = Date.now() + interval;
      setTimeout(step, interval);
      function step() {
          var dt = Date.now() - expected; // the drift (positive for overshooting)
          if (dt > interval) {
              // something really bad happened. Maybe the browser (tab) was inactive?
              // possibly special handling to avoid futile "catch up" run
          }
          … // do what is to be done
      
          expected += interval;
          setTimeout(step, Math.max(0, interval - dt)); // take into account drift
      }
      

      我同意Bergi使用Date,但他的解决方案对我来说有点过头了。我只是想让我的动画时钟(数字和模拟SVG)在第二次更新,而不是在时钟更新中产生明显的跳跃。以下是我在时钟更新函数中输入的代码片段:

          var milliseconds = now.getMilliseconds();
          var newTimeout = 1000 - milliseconds;
          this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout);
      
      它只计算下一个偶数秒的增量时间,并将超时设置为该增量。这会将我的所有时钟对象同步到秒。希望这会有帮助。

      我只是在(特别是第二部分)上做了一点构建,因为我真的很喜欢它的工作方式,但我希望在计时器启动后可以选择停止计时器(比如
      clearInterval()
      几乎可以)。所以。。。我把它包装成了一个构造函数,这样我们就可以用它做“objecty”的事情了

      1.建造师 好的,那么你复制/粘贴

      /**
       * Self-adjusting interval to account for drifting
       * 
       * @param {function} workFunc  Callback containing the work to be done
       *                             for each interval
       * @param {int}      interval  Interval speed (in milliseconds) - This 
       * @param {function} errorFunc (Optional) Callback to run if the drift
       *                             exceeds interval
       */
      function AdjustingInterval(workFunc, interval, errorFunc) {
          var that = this;
          var expected, timeout;
          this.interval = interval;
      
          this.start = function() {
              expected = Date.now() + this.interval;
              timeout = setTimeout(step, this.interval);
          }
      
          this.stop = function() {
              clearTimeout(timeout);
          }
      
          function step() {
              var drift = Date.now() - expected;
              if (drift > that.interval) {
                  // You could have some default stuff here too...
                  if (errorFunc) errorFunc();
              }
              workFunc();
              expected += that.interval;
              timeout = setTimeout(step, Math.max(0, that.interval-drift));
          }
      }
      
      2.实例化 告诉它该做什么以及所有这些

      // For testing purposes, we'll just increment
      // this and send it out to the console.
      var justSomeNumber = 0;
      
      // Define the work to be done
      var doWork = function() {
          console.log(++justSomeNumber);
      };
      
      // Define what to do if something goes wrong
      var doError = function() {
          console.warn('The drift exceeded the interval.');
      };
      
      // (The third argument is optional)
      var ticker = new AdjustingInterval(doWork, 1000, doError);
      
      3.那就。。。东西
      我的意思是,不管怎么说,这对我来说很有效。如果有更好的方法,让我知道。

      这是一个老问题,但我想分享一些我有时使用的代码:

      function Timer(func, delay, repeat, runAtStart)
      {
          this.func = func;
          this.delay = delay;
          this.repeat = repeat || 0;
          this.runAtStart = runAtStart;
      
          this.count = 0;
          this.startTime = performance.now();
      
          if (this.runAtStart)
              this.tick();
          else
          {
              var _this = this;
              this.timeout = window.setTimeout( function(){ _this.tick(); }, this.delay);
          }
      }
      Timer.prototype.tick = function()
      {
          this.func();
          this.count++;
      
          if (this.repeat === -1 || (this.repeat > 0 && this.count < this.repeat) )
          {
              var adjustedDelay = Math.max( 1, this.startTime + ( (this.count+(this.runAtStart ? 2 : 1)) * this.delay ) - performance.now() );
              var _this = this;
              this.timeout = window.setTimeout( function(){ _this.tick(); }, adjustedDelay);
          }
      }
      Timer.prototype.stop = function()
      {
          window.clearTimeout(this.timeout);
      }
      
      自我更正
      setTimeout
      ,可以运行X次(-1表示无限次),可以立即开始运行,如果需要查看
      func()
      运行了多少次,可以使用计数器。很方便


      编辑:注意,这不会进行任何输入检查(比如延迟和重复是否是正确的类型)。如果您想获取计数或更改重复值,您可能需要添加某种get/set函数。

      此处答案中的大多数计时器将延迟预期时间,因为它们设置了“预期”时间将值设置为理想值,并仅考虑浏览器在该点之前引入的延迟。如果您只需要精确的时间间隔,则可以这样做,但如果您是相对于其他事件进行计时,则(几乎)总是会有此延迟

      要更正它,您可以跟踪漂移历史并使用它预测未来的漂移。通过使用此先发制人的更正添加二次调整,漂移的方差将围绕目标时间进行。例如,如果您总是获得20到40ms的漂移,则此调整将在目标时间周围将其移动到-10到+10ms。

      在此基础上,我在我的预测算法中使用了一个滚动中值。用这种方法只取10个样本就可以产生合理的差异

      var interval=200;//毫秒
      var expected=Date.now()+间隔;
      var漂移_历史=[];
      var漂移历史样本=10;
      var漂移校正=0;
      功能计算漂移(arr){
      //计算漂移修正。
      /*
      在这个例子中,我使用了一个简单的中位数。
      您可以使用其他方法,但重要的是不要使用平均值。
      如果用户切换选项卡并返回,则平均值会太高
      对异常值进行加权。
      */
      var values=arr.concat();//复制数组,使其不发生变异
      值。排序(函数(a,b){
      返回a-b;
      });
      if(values.length==0)返回0;
      var half=数学地板(值为1.5/2);
      if(values.length%2)返回值[一半];
      var中位数=(值[一半-1]+值[一半])/2.0;
      返回中值;
      }
      设置超时(步长、间隔);
      函数步骤(){
      var dt=Date.now()-预期;//漂移(对于超调为正值)
      如果(dt>间隔){
      //发生了非常糟糕的事情。可能浏览器(选项卡)处于非活动状态?
      //可能需要特殊处理,以避免徒劳的“追赶”运行
      }
      //做该做的事
      //不要为异常大的值更新历史记录
      如果(dt=漂移\历史\样本){
      漂移历史。移位();
      }    
      }
      预期+=间隔;
      //考虑漂移和预测
      setTimeout(步长,数学最大值(0,间隔-dt-漂移校正));
      }
      的答案准确地指出了问题中计时器不准确的原因。下面是我对一个简单JS计时器的看法,它具有
      开始
      停止
      重置
      获取时间
      方法:

      类计时器{
      构造函数(){
      this.isRunning=false;
      这个,开始
      
      function Timer(func, delay, repeat, runAtStart)
      {
          this.func = func;
          this.delay = delay;
          this.repeat = repeat || 0;
          this.runAtStart = runAtStart;
      
          this.count = 0;
          this.startTime = performance.now();
      
          if (this.runAtStart)
              this.tick();
          else
          {
              var _this = this;
              this.timeout = window.setTimeout( function(){ _this.tick(); }, this.delay);
          }
      }
      Timer.prototype.tick = function()
      {
          this.func();
          this.count++;
      
          if (this.repeat === -1 || (this.repeat > 0 && this.count < this.repeat) )
          {
              var adjustedDelay = Math.max( 1, this.startTime + ( (this.count+(this.runAtStart ? 2 : 1)) * this.delay ) - performance.now() );
              var _this = this;
              this.timeout = window.setTimeout( function(){ _this.tick(); }, adjustedDelay);
          }
      }
      Timer.prototype.stop = function()
      {
          window.clearTimeout(this.timeout);
      }
      
      time = 0;
      this.gameTimer = new Timer( function() { time++; }, 1000, -1);
      
      setDriftlessInterval(() => {
          this.counter--;
      }, 1000);
      
      setDriftlessInterval(() => {
          this.refreshBounds();
      }, 20000);
      
      var remainingTime = 30;
          var elem = document.getElementById('countdown_div');
          var timer = setInterval(countdown, 1000); //set the countdown to every second
          function countdown() {
            if (remainingTime == -1) {
              clearTimeout(timer);
              doSomething();
            } else {
              elem.innerHTML = remainingTime + ' left';
              remainingTime--; //we subtract the second each iteration
            }
          }