有没有比setTimeout更准确的方法来创建Javascript计时器?

时间:2008-10-12 20:38:36

标签: javascript timer

总是让我烦恼的是Javascript中setTimeout()方法的不可预测性。

根据我的经验,计时器在很多情况下非常不准确。由于不准确,我的意思是实际延迟时间似乎或多或少变化250-500ms。虽然这不是一个大量的时间,但是当使用它来隐藏/显示UI元素时,时间可以明显地显而易见。

是否有任何技巧可以确保setTimeout()准确执行(不使用外部API)或这是一个失败的原因?

16 个答案:

答案 0 :(得分:72)

  

是否有任何技巧可以做   确保setTimeout()执行   准确(不求助于   外部API)或这是一个失败的原因?

不,不。你不会接近setTimeout()完全准确的计时器 - 浏览器没有为此设置。 然而,你也不需要依赖它来计时。大多数动画库在几年前就已经解决了这个问题:您使用setTimeout()设置回调,但根据(new Date()).milliseconds(或等效值)的值确定需要完成的操作。这使您可以在较新的浏览器中利用更可靠的计时器支持,同时在旧版浏览器上仍能正常运行。

它还允许您避免使用太多的计时器!这很重要:每个计时器都是一个回调。每个回调都执行JS代码。当JS代码正在执行时,浏览器事件(包括其他回调)会被延迟或丢弃。回调完成后,其他回调必须与其他浏览器事件竞争才有机会执行。因此,处理该间隔的所有待处理任务的一个计时器将比具有重合间隔的两个计时器表现更好,并且(对于短暂超时)优于两个具有重叠超时的计时器!

总结:停止使用setTimeout()实现“一个定时器/一个任务”设计,并使用实时时钟来平滑UI动作。

答案 1 :(得分:32)

REF; http://www.sitepoint.com/creating-accurate-timers-in-javascript/

这个网站大规模救了我。

您可以使用系统时钟来补偿定时器的不准确性。如果你将一个计时函数作为一系列setTimeout调用运行 - 每个实例调用下一个 - 那么你要做的就是保持它的准确性,确切地说它是多么不准确,并从下一次迭代中减去这个差异:

var start = new Date().getTime(),  
    time = 0,  
    elapsed = '0.0';  
function instance()  
{  
    time += 100;  
    elapsed = Math.floor(time / 100) / 10;  
    if(Math.round(elapsed) == elapsed) { elapsed += '.0'; }  
    document.title = elapsed;  
    var diff = (new Date().getTime() - start) - time;  
    window.setTimeout(instance, (100 - diff));  
}  
window.setTimeout(instance, 100);  

此方法可以最大限度地减少漂移并将误差降低90%以上。

它修复了我的问题,希望有所帮助

答案 2 :(得分:10)

我不久前遇到过类似的问题,并提出了一种将requestAnimationFrame与performance.now()相结合的方法,该方法非常有效。

我现在可以将定时器精确到小数点后12位:

    window.performance = window.performance || {};
    performance.now = (function() {
        return performance.now       ||
            performance.mozNow    ||
            performance.msNow     ||
            performance.oNow      ||
            performance.webkitNow ||
                function() {
                    //Doh! Crap browser!
                    return new Date().getTime(); 
                };
        })();

http://jsfiddle.net/CGWGreen/9pg9L/

答案 3 :(得分:4)

shog9的回答几乎就是我所说的,尽管我会添加以下关于UI动画/事件的内容:

如果你有一个应该滑到屏幕上的盒子,向下扩展,然后淡入其内容,不要试图让所有三个事件分开,延迟时间让它们一个接一个地开火 - 使用回调,所以一旦完成第一个事件,它就会调用扩展器,一旦完成它就会调用推子。 jQuery可以轻松完成,我相信其他库也可以。

答案 4 :(得分:4)

如果您需要在给定的时间间隔内获得准确的回调,这个要点可以帮助您:

https://gist.github.com/1185904

答案 5 :(得分:3)

如果您正在使用setTimeout()快速获得浏览器,那么它的UI线程可以赶上它需要执行的任何任务(例如更新选项卡,或者不是显示“长时间运行脚本”对话框),有一个名为Efficient Script Yielding的新API,又名setImmediate(),可能对您有所帮助。

setImmediate()setTimeout()的操作方式非常相似,但如果浏览器没有其他任何操作可以立即运行。在许多使用setTimeout(..., 16)setTimeout(..., 4)setTimeout(..., 0)的情况下(即您希望浏览器运行任何未完成的UI线程任务而不显示长时间运行脚本对话框),您可以简单地将setTimeout()替换为setImmediate(),删除第二个(毫秒)参数。

setImmediate()的区别在于它基本上是一个收益率;如果浏览器有时间在UI线程上(例如,更新选项卡),它将在返回回调之前执行此操作。但是,如果浏览器已经完成所有工作,那么setImmediate()中指定的回调基本上会毫无延迟地运行。

不幸的是,它目前仅在IE9 +中受支持,因为其他浏览器供应商中有一些push back

如果您想使用它,并希望其他浏览器在某些时候实现它,那么可以使用good polyfill

如果您使用setTimeout()进行动画requestAnimationFrame是您最好的选择,因为您的代码会与显示器的刷新率同步。

如果您使用setTimeout()较慢的节奏,例如每300毫秒一次,你可以使用类似于user1213320建议的解决方案,在那里你可以监控你的计时器运行的最后一个时间戳的长度,并补偿任何延迟。一个改进是你可以使用新的High Resolution Time接口(又名window.performance.now())而不是Date.now()来获得当前时间大于毫秒的分辨率。

答案 6 :(得分:2)

你需要在目标时间“爬行”。一些试验和错误将是必要的,但实质上。

设置超时以在所需时间之前100ms完成

使超时处理程序的功能如下:

calculate_remaining_time
if remaining_time > 20ms // maybe as much as 50
  re-queue the handler for 10ms time
else
{
  while( remaining_time > 0 ) calculate_remaining_time;
  do_your_thing();
  re-queue the handler for 100ms before the next required time
}

但你的while循环仍然会被其他进程打断,所以它仍然不完美。

答案 7 :(得分:2)

这是一个演示Shog9建议的示例。这将在6秒内平滑地填充jquery进度条,然后在填充后重定向到不同的页面:

var TOTAL_SEC = 6;
var FRAMES_PER_SEC = 60;
var percent = 0;
var startTime = new Date().getTime();

setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);

function updateProgress() {
    var currentTime = new Date().getTime();

    // 1000 to convert to milliseconds, and 100 to convert to percentage
    percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100;

    $("#progressbar").progressbar({ value: percent });

    if (percent >= 100) {
        window.location = "newLocation.html";
    } else {
        setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
    }                 
}

答案 8 :(得分:1)

这是我为我的音乐项目制作的计时器。定时器在所有设备上都是准确的。

var Timer = function(){
  var framebuffer = 0,
  var msSinceInitialized = 0,
  var timer = this;

  var timeAtLastInterval = new Date().getTime();

  setInterval(function(){
    var frametime = new Date().getTime();
    var timeElapsed = frametime - timeAtLastInterval;
    msSinceInitialized += timeElapsed;
    timeAtLastInterval = frametime;
  },1);

  this.setInterval = function(callback,timeout,arguments) {
    var timeStarted = msSinceInitialized;
    var interval = setInterval(function(){
      var totaltimepassed = msSinceInitialized - timeStarted;
      if (totaltimepassed >= timeout) {
        callback(arguments);
        timeStarted = msSinceInitialized;
      }
    },1);

    return interval;
  }
}

var timer = new Timer();
timer.setInterval(function(){console.log("This timer will not drift."),1000}

答案 9 :(得分:0)

讨厌说出来,但我认为没有办法减轻这种情况。我认为这取决于客户端系统,因此更快的javascript引擎或机器可能会使稍微更准确。

答案 10 :(得分:0)

根据我的经验,失去了努力,即使我认识到js的最小合理时间是大约32-33毫秒。 ...

答案 11 :(得分:0)

这里肯定存在限制。为了给你一些看法,谷歌刚刚发布的Chrome浏览器足够快,可以在15-20毫秒内执行setTimeout(function(){},0),而较旧的Javascript引擎需要数百毫秒来执行该功能。虽然setTimeout使用毫秒,但此时没有javascript虚拟机可以执行具有该精度的代码。

答案 12 :(得分:0)

Dan,根据我的经验(包括在JavaScript中实现SMIL2.1语言,其中时间管理是主题)我可以向您保证,您实际上从不需要高精度的setTimeout或setInterval。

无论如何重要的是setTimeout / setInterval在排队时执行的顺序 - 并且总是完美无缺。

答案 13 :(得分:0)

JavaScript超时的实际限制为10-15ms(我不知道你要做什么来获得200ms,除非你执行185ms的实际js)。这是因为Windows具有15ms的标准定时器分辨率,唯一可行的方法是使用Windows的更高分辨率的定时器,这是一个系统范围的设置,因此可以与系统上的其他应用程序拧紧并且还可以缩短电池寿命(Chrome具有英特尔在这个问题上的一个错误。)

10-15ms的事实标准是由于人们在网站上使用0ms超时,但后来假设假设超时为10-15ms(例如假设60fps的js游戏但是没有delta的0ms /帧)逻辑,所以游戏/网站/动画比预期快几个数量级)。为了解决这个问题,即使在没有Windows计时器问题的平台上,浏览器也会将计时器分辨率限制为10毫秒。

答案 14 :(得分:0)

以下是我使用的内容。既然它是JavaScript,我会发布我的前端和node.js解决方案:

对于两者,我使用相同的十进制舍入功能,我强烈建议你保持一臂之力,原因是:

const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)

places - 要舍入的小数位数,这应该是安全的并且应该避免浮点数的任何问题(某些数字如1.0000000000005~可能有问题)。我花时间研究转换为毫秒的高分辨率计时器提供的最佳小数舍入方法。

that + symbol - 它是一个一元运算符,将操作数转换为数字,与Number()几乎相同

  

浏览器

const start = performance.now()

// I wonder how long this comment takes to parse

const end = performance.now()

const result = (end - start) + ' ms'

const adjusted = round(2, result) // see above rounding function
  

的node.js

// Start timer
const startTimer = () => process.hrtime()

// End timer
const endTimer = (time) => {
    const diff = process.hrtime(time)
    const NS_PER_SEC = 1e9
    const result = (diff[0] * NS_PER_SEC + diff[1])
    const elapsed = Math.round((result * 0.0000010))
    return elapsed
}

// This end timer converts the number from nanoseconds into milliseconds;
// you can find the nanosecond version if you need some seriously high-resolution timers.

const start = startTimer()

// I wonder how long this comment takes to parse

const end = endTimer(start)

console.log(end + ' ms')

答案 15 :(得分:-1)

您可以考虑使用html5 webaudio clock使用系统时间以获得更高的准确性