有没有更准确的方法来创build比setTimeout的Javascript计时器?
一直困扰着我的是Javascript中的setTimeout()
方法是多么的不可预测。
根据我的经验,在许多情况下计时器是非常不准确的。 由于不准确,我的意思是实际的延迟时间差不多是250-500毫秒。 虽然这不是一个大量的时间,当用它来隐藏/显示UI元素时,可以明显地看到时间。
是否有任何技巧可以确保setTimeout()
准确执行(无需使用外部API),或者这是一个失败的原因?
是否有任何技巧可以确保
setTimeout()
准确执行(无需使用外部API),或者这是一个失败的原因?
不,不, 你不会用setTimeout()
获得任何接近完美定时器的东西 – 浏览器没有设置。 但是 ,您也不需要依赖它来计时。 大多数animation库在几年前就已经明白了这一点:你用setTimeout()
设置了一个callback函数,但是根据(new Date()).milliseconds
(或者等价的)值确定需要做什么。 这使您可以在更新的浏览器中利用更可靠的计时器支持,同时在旧浏览器上依然可以正常运行。
它也可以让你避免使用太多的计时器 ! 这很重要:每个计时器都是callback。 每个callback都执行JS代码。 当JS代码正在执行时,浏览器事件(包括其他callback)被延迟或丢弃。 callback完成后,其他callback必须与其他浏览器事件竞争才有机会执行。 因此,处理所有待处理任务的定时器的执行时间要比两个定时器的间隔一致,并且(对于短暂的超时)要好于两个具有重叠超时的定时器!
总结:停止使用setTimeout()
来实现“一个定时器/一个任务”的devise,并使用实时时钟来平滑UI操作。
。
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%以上。
它解决了我的问题,希望它有帮助
我不久前遇到了类似的问题,并提出了一个将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(); }; })();
shog9的答案几乎是我所说的,虽然我会添加以下有关UIanimation/事件:
如果你有一个盒子应该滑到屏幕上,向下扩展,然后淡入其内容,不要试图让所有三个事件分开,延迟时间,使他们一个接一个地开火 – 使用callback,所以一次第一个事件是滑动它调用扩展器,一旦完成它调用推子。 jQuery可以很容易地做到这一点,我相信其他库也可以。
如果您需要在给定的时间间隔内获得准确的callback,这个要点可以帮助您:
你需要在目标时间“爬上”。 一些尝试和错误将是必要的,但在本质上。
设置一个超时时间,在所需时间前100毫秒完成
使超时处理函数如下所示:
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循环仍然可以被其他进程中断,所以它仍然不是完美的。
如果使用setTimeout()
快速生成浏览器,那么它的UI线程可以赶上任何它需要做的任务(比如更新一个标签,或者不显示长时间运行脚本对话框),还有一个新的称为Efficient Script Yielding的 API,又名setImmediate()
,可能对你来说效果更好。
setImmediate()
与setTimeout()
非常类似,但是如果浏览器没有其他操作,它可能立即运行。 在许多使用setTimeout(..., 16)
或setTimeout(..., 4)
或setTimeout(..., 0)
(即,您希望浏览器运行任何未完成的UI线程任务,而不显示长时间运行脚本对话框),您可以简单地用setImmediate()
replacesetTimeout()
, setImmediate()
删除第二个参数(毫秒)。
与setImmediate()
的区别在于它基本上是一个收益率; 如果浏览器有时间在UI线程上(例如,更新一个标签),它会在返回到你的callback之前这样做。 但是,如果浏览器已经全部赶上了它的工作,那么在setImmediate()
指定的callback本质上将毫无延迟地运行。
不幸的是,目前只支持IE9 +,因为其他浏览器厂商有一些推动 。
如果你想使用它,并希望其他浏览器在某些时候实现它,那么有一个好的polyfill可用。
如果您将setTimeout()
用于animation ,则requestAnimationFrame是您的最佳select,因为您的代码将与显示器的刷新速率同步运行。
如果您在较慢的节奏上使用setTimeout()
例如,每300毫秒一次),则可以使用与user1213320build议类似的解决scheme,您可以在其中监视计时器上次运行的时间,并补偿延迟。 一种改进是,您可以使用新的高分辨率时间接口(又名window.performance.now()
)而不是Date.now()
来获取当前时间的大于毫秒的分辨率。
这是一个演示Shog9的build议的例子。 这会在6秒钟内平滑地填充一个jquery进度条,然后一旦填充就redirect到一个不同的页面:
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); } }
这是我为我的音乐项目制作的一个计时器,它是做这件事的。 在所有设备上准确的定时器。
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}
讨厌这样说,但我不认为有办法来缓解这一点。 不过,我认为这取决于客户端系统,所以更快的JavaScript引擎或机器可能会使其稍微更准确。
根据我的经验,这是失去了努力,即使我认识到的最小合理的时间js的行为是在32-33毫秒左右。 …
这里肯定有一个限制。 为了给你一些透视,Google刚刚发布的Chrome浏览器速度足够快,可以在15-20毫秒内执行setTimeout(function(){},0),而较老的Javascript引擎则需要花费数百毫秒来执行该函数。 虽然setTimeout使用毫秒,但是此时没有任何JavaScript虚拟机可以以这种精度执行代码。
Dan,根据我的经验(包括在JavaScript中实现SMIL2.1语言,时间pipe理是主题),我可以向您保证,您实际上从来不需要setTimeout或setInterval的高精度。
然而,setTimeout / setInterval在排队时执行的顺序是什么,而且总是完美的。
JavaScript超时有一个事实上的限制10-15毫秒(我不知道你在做什么得到200毫秒,除非你正在做185毫秒的实际js执行)。 这是由于windows有一个标准的计时器分辨率为15毫秒,唯一的办法做得更好是使用Windows的更高分辨率的计时器这是一个系统范围的设置,因此可以与系统上的其他应用程序拧紧,也咀嚼电池寿命(Chrome已英特尔在这个问题上的一个错误)。
事实上10-15ms的标准是由于人们在网站上使用0ms超时,但是接着以假定假设10-15ms超时的方式进行编码(例如假定60fps的js游戏,而不要求delta逻辑的0ms /帧,所以游戏/网站/animation比预期快几个数量级)。 为了解决这个问题,即使在没有Windows定时器问题的平台上,浏览器也将定时器分辨率限制在10ms。
这是我使用的。 既然是JavaScript,我会发布我的前端和node.js解决scheme:
对于这两个,我使用相同的小数四舍五入function,我强烈build议你保持武器的长度,因为理由:
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')
您可以考虑使用使用系统时间的html5 webaudio时钟 ,以获得更高的准确性