如何在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
}
}