Is there a more accurate way to create a Javascrip

2018-12-31 17:55发布

Something that has always bugged me is how unpredictable the setTimeout() method in Javascript is.

In my experience, the timer is horribly inaccurate in a lot of situations. By inaccurate, I mean the actual delay time seems to vary by 250-500ms more or less. Although this isn't a huge amount of time, when using it to hide/show UI elements the time can be visibly noticeable.

Are there any tricks that can be done to ensure that setTimeout() performs accurately (without resorting to an external API) or is this a lost cause?

16条回答
若你有天会懂
2楼-- · 2018-12-31 18:19

To my experience it is lost effort, even as the smallest reasonable amount of time I ever recognized js act in is around 32-33 ms. ...

查看更多
无与为乐者.
3楼-- · 2018-12-31 18:20

You could consider using the html5 webaudio clock which uses the system time for better accuracy

查看更多
不流泪的眼
4楼-- · 2018-12-31 18:22

Here's an example demoing Shog9's suggestion. This fills a jquery progress bar smoothly over 6 seconds, then redirects to a different page once it's filled:

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);
    }                 
}
查看更多
十年一品温如言
5楼-- · 2018-12-31 18:25

I had a similar problem not long ago and came up with an approach which combines requestAnimationFrame with performance.now() which works very effectively.

Im now able to make timers accurate to approx 12 decimal places:

    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/

查看更多
梦该遗忘
6楼-- · 2018-12-31 18:25

You need to "creep up" on the target time. Some trial and error will be necessary but in essence.

Set a timeout to complete arround 100ms before the required time

make the timeout handler function like this:

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
}

But your while loop can still get interrupted by other processes so it's still not perfect.

查看更多
深知你不懂我心
7楼-- · 2018-12-31 18:28

If you're using setTimeout() to yield quickly to the browser so it's UI thread can catch up with any tasks it needs to do (such as updating a tab, or to not show the Long Running Script dialog), there is a new API called Efficient Script Yielding, aka, setImmediate() that may work a bit better for you.

setImmediate() operates very similarly to setTimeout(), yet it may run immediately if the browser has nothing else to do. In many situations where you are using setTimeout(..., 16) or setTimeout(..., 4) or setTimeout(..., 0) (i.e. you want the browser to run any outstanding UI thread tasks and not show a Long Running Script dialog), you can simply replace your setTimeout() with setImmediate(), dropping the second (millisecond) argument.

The difference with setImmediate() is that it is basically a yield; if the browser has sometime to do on the UI thread (e.g., update a tab), it will do so before returning to your callback. However, if the browser is already all caught up with its work, the callback specified in setImmediate() will essentially run without delay.

Unfortunately it is only currently supported in IE9+, as there is some push back from the other browser vendors.

There is a good polyfill available though, if you want to use it and hope the other browsers implement it at some point.

If you are using setTimeout() for animation, requestAnimationFrame is your best bet as your code will run in-sync with the monitor's refresh rate.

If you are using setTimeout() on a slower cadence, e.g. once every 300 milliseconds, you could use a solution similar to what user1213320 suggests, where you monitor how long it was from the last timestamp your timer ran and compensate for any delay. One improvement is that you could use the new High Resolution Time interface (aka window.performance.now()) instead of Date.now() to get greater-than-millisecond resolution for the current time.

查看更多
登录 后发表回答