有一件事情一直让我感到烦恼,那就是Javascript中的setTimeout()
方法总是不可预测。
在我的经验中,这个计时器在许多情况下都非常不准确。所谓的不准确,是指实际延迟时间似乎多或少变化了250-500毫秒。虽然这并不是很长的时间,但当用它来隐藏/显示UI元素时,时间可能是明显可见的。
有没有什么技巧可以确保setTimeout()
执行得准确(而不需要使用外部API),还是这个问题无解?
有一件事情一直让我感到烦恼,那就是Javascript中的setTimeout()
方法总是不可预测。
在我的经验中,这个计时器在许多情况下都非常不准确。所谓的不准确,是指实际延迟时间似乎多或少变化了250-500毫秒。虽然这并不是很长的时间,但当用它来隐藏/显示UI元素时,时间可能是明显可见的。
有没有什么技巧可以确保setTimeout()
执行得准确(而不需要使用外部API),还是这个问题无解?
setTimeout()
的精确性(而不需要使用外部API)?这是不可能实现的。浏览器没有这样的设置。然而,您也不需要依赖它来计时。大多数动画库在很多年前就已经解决了这个问题:您可以使用setTimeout()
设置回调,但根据(new Date()).milliseconds
(或等效值)的值确定需要执行的任务。这使您可以利用新版本浏览器中更可靠的定时器支持,同时在旧版本浏览器上表现得合适。setTimeout()
来实现“一个定时器/一个任务”的设计,使用实时时钟平滑UI操作。requestAnimationFrame
...另请参见Chris的这个答案。这不是一个简单的解决方案,但它满足了OP的没有外部API的标准。 - Stijn de WittREF: http://www.sitepoint.com/creating-accurate-timers-in-javascript/
This site helped me a lot.
To compensate for timer inaccuracy, you can use the system clock. If you run a timing function as a series of setTimeout calls, with each instance calling the next, you only need to calculate how inaccurate it is and subtract that difference from the next iteration to keep it accurate.
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%。
它解决了我的问题,希望能对你有所帮助。
elapsed
变量? - Anton RudeshkodoTimer
函数正是我所需要的,它非常准确。 - Luc我不久前也遇到了类似的问题,并采用了一种结合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();
};
})();
https://gist.github.com/1185904
function interval(duration, fn){
var _this = this
this.baseline = undefined
this.run = function(){
if(_this.baseline === undefined){
_this.baseline = new Date().getTime()
}
fn()
var end = new Date().getTime()
_this.baseline += duration
var nextTick = duration - (end - _this.baseline)
if(nextTick<0){
nextTick = 0
}
_this.timer = setTimeout(function(){
_this.run(end)
}, nextTick)
}
this.stop = function(){
clearTimeout(_this.timer)
}
}
shog9的回答已经涵盖了我想说的,不过关于UI动画/事件,我还想补充以下内容:
如果你有一个方框应该滑动到屏幕上,向下展开,然后淡入其内容,请不要尝试使所有三个事件分别延迟一段时间后依次触发 - 使用回调函数,这样一旦第一个事件完成滑动,它就会调用扩展器,一旦完成,它就会调用淡化器。jQuery可以轻松实现此操作,我相信其他库也可以。
setTimeout()
快速地让浏览器放弃执行,以便其用户界面线程可以赶上需要执行的任何任务(例如更新选项卡或不显示长时间运行脚本对话框),那么有一个称为 Efficient Script Yielding 的新 API,即 setImmediate()
,可能更适合您。
setImmediate()
的操作方式非常类似于 setTimeout()
,但如果浏览器没有其他任务要执行,它可能会立即运行。 在许多情况下,您使用 setTimeout(..., 16)
或 setTimeout(..., 4)
或 setTimeout(..., 0)
(即,您希望浏览器运行任何未完成的用户界面线程任务并不显示长时间运行脚本对话框),您可以将 setTimeout()
替换为 setImmediate()
,并删除第二个(毫秒)参数。setImmediate()
的区别在于,它基本上是一个yield;如果浏览器在UI线程上有一些事情要做(例如更新选项卡),它将在返回到您的回调之前执行此操作。然而,如果浏览器已经完成了所有工作,那么在setImmediate()
中指定的回调函数将基本上立即运行。setTimeout()
进行动画,requestAnimationFrame是您最好的选择,因为您的代码将与监视器的刷新率同步运行。setTimeout()
,则可以使用类似于用户1213320建议的解决方案来监视计时器上次运行的时间戳,并补偿任何延迟。一种改进方法是,您可以使用新的高分辨率时间接口(又称window.performance.now()
),而不是Date.now()
来获取当前时间的毫秒级精度以上的分辨率。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 循环仍然可能会被其他进程中断,因此它仍然不完美。
这里是一个演示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);
}
}
setTimeout()
。我会相应地编辑。 - Deanvar 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}