xRo*_*bot 39 javascript time setinterval
我需要创建一个简单但准确的计时器.
这是我的代码:
var seconds = 0;
setInterval(function() {
timer.innerHTML = seconds++;
}, 1000);
Run Code Online (Sandbox Code Playgroud)
在3600秒之后,它打印约3500秒.
为什么不准确?
如何创建准确的计时器?
Ber*_*rgi 105
为什么不准确?
因为你正在使用setTimeout()或setInterval().他们不可信任,没有准确的保证.它们被允许 任意滞后,并且它们不会保持恒定的速度但往往会漂移(正如您所观察到的那样).
如何创建准确的计时器?
使用该Date对象来获得(毫秒)准确的当前时间.然后将逻辑基于当前时间值,而不是计算回调执行的频率.
对于一个简单的定时器或时钟,跟踪时间的差异明确:
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
Run Code Online (Sandbox Code Playgroud)
现在,这有可能跳跃值的问题.当间隔滞后了一下,之后执行回调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
}
Run Code Online (Sandbox Code Playgroud)
Blo*_*orf 13
此处答案中的大多数计时器都会滞后于预期时间,因为它们将“预期”值设置为理想值,并且仅考虑浏览器在此之前引入的延迟。如果您只需要准确的时间间隔,这很好,但如果您是相对于其他事件的时间,那么您(几乎)总是会有这种延迟。
要纠正它,您可以跟踪漂移历史并使用它来预测未来的漂移。通过使用这种先发制人的校正添加二次调整,漂移的方差以目标时间为中心。例如,如果您总是有 20 到 40 毫秒的漂移,则此调整会将其移动到目标时间附近的 -10 到 +10 毫秒。
基于Bergi 的回答,我在预测算法中使用了滚动中位数。使用此方法仅采集 10 个样本即可产生合理的差异。
var interval = 200; // ms
var expected = Date.now() + interval;
var drift_history = [];
var drift_history_samples = 10;
var drift_correction = 0;
function calc_drift(arr){
// Calculate drift correction.
/*
In this example I've used a simple median.
You can use other methods, but it's important not to use an average.
If the user switches tabs and back, an average would put far too much
weight on the outlier.
*/
var values = arr.concat(); // copy array so it isn't mutated
values.sort(function(a,b){
return a-b;
});
if(values.length ===0) return 0;
var half = Math.floor(values.length / 2);
if (values.length % 2) return values[half];
var median = (values[half - 1] + values[half]) / 2.0;
return median;
}
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
// don't update the history for exceptionally large values
if (dt <= interval) {
// sample drift amount to history after removing current correction
// (add to remove because the correction is applied by subtraction)
drift_history.push(dt + drift_correction);
// predict new drift correction
drift_correction = calc_drift(drift_history);
// cap and refresh samples
if (drift_history.length >= drift_history_samples) {
drift_history.shift();
}
}
expected += interval;
// take into account drift with prediction
setTimeout(step, Math.max(0, interval - dt - drift_correction));
}Run Code Online (Sandbox Code Playgroud)
Tom*_*ała 12
Bergi的回答准确指出了问题中的计时器不准确的原因。以下是一张上用一个简单的JS计时器start,stop,reset和getTime方法:
class Timer {
constructor () {
this.isRunning = false;
this.startTime = 0;
this.overallTime = 0;
}
_getTimeElapsedSinceLastStart () {
if (!this.startTime) {
return 0;
}
return Date.now() - this.startTime;
}
start () {
if (this.isRunning) {
return console.error('Timer is already running');
}
this.isRunning = true;
this.startTime = Date.now();
}
stop () {
if (!this.isRunning) {
return console.error('Timer is already stopped');
}
this.isRunning = false;
this.overallTime = this.overallTime + this._getTimeElapsedSinceLastStart();
}
reset () {
this.overallTime = 0;
if (this.isRunning) {
this.startTime = Date.now();
return;
}
this.startTime = 0;
}
getTime () {
if (!this.startTime) {
return 0;
}
if (this.isRunning) {
return this.overallTime + this._getTimeElapsedSinceLastStart();
}
return this.overallTime;
}
}
const timer = new Timer();
timer.start();
setInterval(() => {
const timeInSeconds = Math.round(timer.getTime() / 1000);
document.getElementById('time').innerText = timeInSeconds;
}, 100)Run Code Online (Sandbox Code Playgroud)
<p>Elapsed time: <span id="time">0</span>s</p>Run Code Online (Sandbox Code Playgroud)
该代码段还包括针对您的问题的解决方案。因此seconds,我们不是每 1000 毫秒间隔递增变量,而是启动计时器,然后每 100 毫秒*我们只是从计时器中读取经过的时间并相应地更新视图。
* - 使其比 1000 毫秒更准确
为了使您的计时器更准确,您必须四舍五入
Leo*_*ams 11
我只是建立在Bergi的答案(特别是第二部分)上,因为我真的很喜欢它的完成方式,但是我想要一旦它启动就停止计时器(就像clearInterval()几乎一样).Sooo ...我把它包装成一个构造函数,所以我们可以用它做'对象'的事情.
好的,所以你复制/粘贴那个......
/**
* 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));
}
}
Run Code Online (Sandbox Code Playgroud)
告诉它该做什么以及所有......
// 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);
Run Code Online (Sandbox Code Playgroud)
// You can start or stop your timer at will
ticker.start();
ticker.stop();
// You can also change the interval while it's in progress
ticker.interval = 99;
Run Code Online (Sandbox Code Playgroud)
我的意思是,无论如何它对我有用.如果有更好的方法,那就知道了.
这是一个在窗口隐藏时暂停的解决方案,并且可以使用中止控制器取消。
function animationInterval(ms, signal, callback) {
const start = document.timeline.currentTime;
function frame(time) {
if (signal.aborted) return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - performance.now();
setTimeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
Run Code Online (Sandbox Code Playgroud)
用法:
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it sometime later:
controller.abort();
Run Code Online (Sandbox Code Playgroud)
小智 7
我同意 Bergi 使用 Date 的观点,但他的解决方案对我来说有点矫枉过正。我只是想让我的动画时钟(数字和模拟 SVG)在第二个更新,而不是超限或不足运行,从而在时钟更新中产生明显的跳跃。这是我放在时钟更新函数中的代码片段:
var milliseconds = now.getMilliseconds();
var newTimeout = 1000 - milliseconds;
this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout);
Run Code Online (Sandbox Code Playgroud)
它只是计算下一个偶数秒的增量时间,并将超时设置为该增量。这会将我所有的时钟对象同步到第二个。希望这是有帮助的。
小智 7
该计时器需要两个必需的参数,一个以赫兹为单位的频率,以及一个最多可以接受四个参数的回调:
Timer 实例具有三种 API 方法:
timer.stop():立即(并且永久)终止计时器。返回下一(已取消)帧的帧索引。timer.adapt(Number):采用以赫兹为单位的频率,并从下一帧开始调整计时器以适应该频率。返回隐含的时间间隔(以毫秒为单位)。timer.redefine(Function): 进行新的回调。将其与当前回调交换。影响下一帧。返回undefined。注意:所有时间均基于performance.now,并以页面加载后的毫秒数表示。
注意:该tick方法this显式传递(as self)以解决通过调用该方法时的this引用问题。windowticksetTimeout
class ProgrammableTimer {
constructor(hertz, callback) {
this.target = performance.now(); // target time for the next frame
this.interval = 1 / hertz * 1000; // the milliseconds between ticks
this.callback = callback;
this.stopped = false;
this.frame = 0;
this.tick(this);
}
tick(self) {
if (self.stopped) return;
const currentTime = performance.now();
const currentTarget = self.target;
const currentInterval = (self.target += self.interval) - currentTime;
setTimeout(self.tick, currentInterval, self);
self.callback(self.frame++, currentTime, currentTarget, self);
}
stop() { this.stopped = true; return this.frame }
adapt(hertz) { return this.interval = 1 / hertz * 1000 }
redefine(replacement) { this.callback = replacement }
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
46423 次 |
| 最近记录: |