当前位置: 首页 > 知识库问答 >
问题:

用Javascript创建一个精确的计时器,但我有时会得到一个负的间隔时间

贾建茗
2023-03-14

我不能使用大量的NPM库来获得精确的计时器,因为它依赖于我没有使用的节点...但是公式非常简单。这里有一个我正在使用的JSfiddle:

http://jsfiddle.net/zryNf/9/

setPercentageBar(page) {
    if (this.state.pagesById[page.id].active) {
      let pagesByIdCopy = Object.assign({}, this.state.pagesById);
      let pageCopy = pagesByIdCopy[page.id];

      let percent = (100.0 / parseFloat(page.durationTime)) / 10;
      pageCopy.percentage += percent;
      this.setState({pagesById: pagesByIdCopy});

      pageCopy.progressBarNextAt += this.progressBarTime;  // this.progressBarTime = 100

      console.log('start time');
      console.log(pageCopy.progressBarStart);
      console.log('next at');
      console.log(pageCopy.progressBarNextAt);
      console.log('drift');
      console.log((new Date().getTime() - pageCopy.progressBarStart) % 100);
      console.log('Date().getTime():');
      let currTime = new Date().getTime();
      console.log(currTime);
      console.log('interval time');
      console.log(pageCopy.progressBarNextAt - currTime);
      console.log('--------------------------------------------------------\n');

      if (pageCopy.percentage < 100) {
        pageCopy.progressBarTimer = setTimeout(() => this.setPercentageBar(pageCopy),
          pageCopy.progressBarNextAt - currTime);
      }
    }
  }

这不是我在React中做的代码,但它是完全相同的公式。

我除了使用100毫秒的延迟,因为我正在使用它来更新进度条,我需要进度条顺利推出。

我在记录开始时间、下一个时间、漂移和间隔时间,它总是开始得很好。但是在某个时候...间隔时间变成负数,我不知道为什么!然后从那时起,一切都开始混乱。我尝试了这个精确计时器的多种公式,最终每次都会发生。一旦间隔时间变成负数...他们都变成负数。

有人知道为什么吗?我能做些什么来修复它?

下面是一些示例日志

https://pastebin.com/Cpfpf1Gp

编辑:我最多有5个计时器在100ms每个

共有2个答案

齐运诚
2023-03-14

问题中的代码似乎采取了一些非常简单的方法,使其变得有点复杂。与其尝试用“漂移”计算来弥补这种复杂性,我们为什么不采取更简单的方法呢?

关键是要将这两件事分离开来:

  1. 何时更新进度条

相反,我们只需要一个update()函数,它可以在任何时候调用,并且总是做正确的事情。它的计算完全不取决于我们调用它的频率或时间。

此外,当您使用setTimeout()setInterval()进行动画时,您可能会得到一些相当不稳定的结果。我们可以使用readestAnimationFrame()来代替更流畅的动画。(它仍然不完美,但它比您使用计时器得到的更好。)

所以它可能看起来像这样:

function progressBar( duration ) {
    setValue( 0 );
    var startTime = +new Date;
    update();
    function update() {
        var elapsed = +new Date - startTime;
        if( elapsed > duration ) elapsed = duration;
        if( elapsed < duration ) requestAnimationFrame( update );
        setValue( elapsed / duration );
    }
    function setValue( fraction ) {
        var percent = Math.floor( fraction * 100 );
        document.getElementById('percent').innerHTML =
            percent + '%';
        document.getElementById('filler').style.width =
            fraction * 100 + '%';
    }
}

progressBar( 5000 );
#border {
    border-color: black;
    border-width: 1px;
    width: 100%;
    height: 20px;
}

#filler {
    background-color: green;
    width: 0;
    height: 100%;
}
<div id="border">
  <div id="filler">
      &nbsp;
  </div>
</div>
<div id="percent"><div>
乌翰学
2023-03-14

不能保证setTimeout回调函数将在所需的时间执行。如果此时其他JavaScript代码正在运行,则必须首先运行到完成,然后才能处理事件队列中的下一个事件,计时器事件就是这样的事件。

所以你很可能会遇到以下情况:

  • 在0ms:currTime为0,进度BarNextAt为100,然后调用setTimeout(进度BarNextAt,100)。一切都很完美。
  • 1ms:事件队列中的下一个事件是正在发生的实际处理的回调
  • 在220ms:回调结束,事件队列中的下一个事件是计时器(延迟),并且您的函数setPercentageBar第二次被调用
  • 在221ms时:进度BarNextAt从100增加到200,这是不够的,因为我们已经过了那个时刻。您打印的间隔时间为-21。

甚至可以在计时器事件被处理之前以微任务的形式安排一些异步代码。例如,如果第二个项目符号中的处理有一些立即解决的promise,相应的那么回调函数仍将作为该任务的一部分执行,只有当所有这些都执行完毕时,计时器事件才会轮到它。

还要注意,非JS因素可能会影响某项任务所需的时间。如果机器负载过重,操作系统会重新分配资源,这可能会影响本来很轻的JavaScript任务。当相关窗口不在前台时,浏览器也倾向于延迟计时器事件的处理。还有其他原因。

 类似资料:
  • 为了使准确,我喜欢@Tony Docherty On CR建议的逻辑和示例。这是链接。 为了一次又一次地突出显示给定的单词,总是会有几微秒的延迟。如果我有要突出显示的单词,比如说:“你好,你好”,每个单词的值分别是(延迟):200300400毫秒,那么计时器实际花费的时间总是更多。比如说,如果我有很多单词的话,需要216毫秒,而不是200毫秒。。最后,额外的延迟是显而易见的。 我必须突出显示每个字

  • 问题内容: 对于这个问题,我很抱歉,但是我已经阅读了很多东西,而且看来我还不懂如何做一个计时器。所以我发布我的代码: 我正在尝试使该对象每200毫秒朝目标移动一次。我没有自我尝试过,它给了我同样的错误: 我不知道如何将计时器连接到带有参数的函数。我以为我没有正确使用SLOT参数,但这给了我这些错误。我想这都是错的。我将不胜感激:) 问题答案: 使用新样式的信号,它们更容易理解。 交换- 与- 一个

  • 问题内容: 为了准确起见,我喜欢@Tony Docherty On CR建议的逻辑和示例。这是链接。 为了突出显示给定的单词,一次又一次地总是有几微秒的延迟。如果我要突出显示一些单词:“ hello how are”,并且每个单词的值分别(延迟)为:200,300,400 ms,那么计时器实际花费的时间总是更多。说而不是200毫秒,而是216毫秒。像这样,如果我有很多话……最后,额外的延迟是显而易

  • 问题内容: 我创建了一个计时器,该计时器在按下按钮时启动,上面是运行的代码。谁能帮我创建一个计数为30的计时器?现在,当我运行它时,在标签中设置文本“ 30”,但我希望它从0开始并一直计数到30。 问题答案: 每次您的计时器运行时,它都会执行从0到30的循环,因此仅在循环结束时才刷新UI。您需要将i保留为成员,并在每次这样调用该方法时对其进行更新: 当然,一旦达到i = 30,您就应该取消时间,否

  • 间隔有开始和结束时间。间隔可能重叠。可能有几个包含时间t的间隔。只返回其中一个就可以了。 这是一个面试问题。我能够解决这个问题,方法是根据结束对间隔进行排序,根据开始对另一个时间进行排序,并取具有匹配开始和结束的间隔的交点。显然有更多优化的解决方案。 这里有一个例子:[1,5][2,10][3,6][2,9],目标是8。在这种情况下,[2,10]和[2,9]中的任何一个都是正确答案。 我想问题的关

  • 首先,我使用Jalali/波斯日期和倒计时jQuery插件对我不起作用。对于ex,插件必须输入公历日期,如2017/5/2,但波斯/贾拉利日期是1396/2/17。 我有多个datetime,指定了天、小时、分钟和秒。例: 3天13小时4分25秒 23天2小时41分3秒... 我有几天、几小时、几分钟和几秒时间, 我想在一页上为他们的日期倒计时(他们的日期是白天的) 当页面中只有一个日期时,我使用