requestAnimationFrame is passing unexpected parameters in IE10 - javascript

So I've been a good net citizen, using feature detection to see whether the browser supports requestAnimationFrame and only fall back to a setTimeout-based solution otherwise (something around the lines of Paul Irish's famous post).
var NOW = Date.now || function () { return new Date.getTime(); };
var reqAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
/* ... || */
function (callback) {
setTimeout(function () { callback(NOW()); }, 1000 / 60);
};
var previousTime = NOW();
function animStep(time) {
var timePassed = time - previousTime;
myCharacter.move(myCharacter.speed * timePassed);
previousTime = time;
reqAnimationFrame(animStep);
}
// start the animation
reqAnimationFrame(animStep);
This worked great everywhere until Internet Explorer 10 came along. In IE10, the time parameter passed doesn't seem to have anything to do with the current time, screwing up the calculation of timePassed.
What's going on?

All (as far as I know) other browsers that implement requestAnimationFrame go by the specification in the (at the time of writing) current Working Draft:
Let time be [the redraw time] expressed as the number of milliseconds since 1970-01-01T00:00:00Z.
That's representing time precisely like your NOW() does.
IE10 however goes by the spec in the current Editor's Draft:
Let time be the result of invoking the now method of the Performance interface within this context.
Which essentially means the number of milliseconds since the browser loaded this page (it also means the the measurement is more precise, since performance.now returns fractional milliseconds).
And thus when you calculate timePassed for the first time in IE10, you are getting something like negative 43 years.
Fortunately, because the value passed to the requestAnimationFrame callback has the same unit in either case, just a different point of reference, you can easily adjust to this.
There are three possibilities:
You could just throw away the very first animation step, using it only to set previousTime, but not doing anything else.
You could ignore the passed parameter and use your NOW() (or performance.now) every time, thus always having the same same point of reference.
Or you could change the start of the animation to this:
// start the animation
reqAnimationFrame(function (t) {
previousTime = t - (NOW() - previousTime);
animStep(t);
);
This will make the calculation (including the first one) of timePassed correct no matter which spec the browser follows. And since it changes only the very first invocation, you don't have any additional overhead in the long run either.

Related

Get Navigation Timing backward/forward compatible - Convert from epoch to HR time

Let's introduce by a note from www.w3.org including two important links to compare.
The PerformanceTiming interface was defined in [NAVIGATION-TIMING] and
is now considered obsolete. The use of names from the
PerformanceTiming interface is supported to remain backwards
compatible, but there are no plans to extend this functionality to
names in the PerformanceNavigationTiming interface defined in
[NAVIGATION-TIMING-2] (or other interfaces) in the future.
I have made a function to get a Navigation Time that should be both backward and forward compatible, because we are in the middle era of transforming to level 2. So this function to get a time from an event name works in Chrome but not Firefox:
function nav(eventName) {
var lev1 = performance.timing; //deprecated unix epoch time in ms since 1970
var lev2 = performance.getEntriesByType("navigation")[0]; //ms since page started to load. (since performance.timing.navigationStart)
var nav = lev2 || lev1; //if lev2 is undefined then use lev1
return nav[eventName]
}
Explanation: When there is no "navigation" entry this falls back to the deprecated way to do navigation timing based on Unix epoch time time in milliseconds since 1970 (lev1), while the new way (lev2) is HR time in milliseconds since the current document navigation started to load, that is useful together with User Timing that always have had the HR time format.
How can we get the function return HR time in all cases?
When I see a number with more than 10 digits without a period I know it is a time got from the deprecated Navigation Timing level 1. All other test cases give decimal point numbers meaning it is HR times with higher precision. The biggest issue is that they have different time origin.
I have gone through confusion, trial errors and frustrated serching (MDN has not updated to level 2) to confirm and state that:
Navigation Timing Level 1 use unix epoch time and the rest...
Navigation Timing Level 2 use HR time
User Timing Level 1 use HR time
User Timing Level 2 use HR time
Also performance.now() has HR time both in Chrome and Firefox.
How to convert unix epoch time to HR time?
SOLVED .:
The code is corrected by help from Amadan.
See comments in tha accepted answer.
function nav(eventName, fallback) {
var lev1 = performance.timing; //deprecated unix epoch time in ms since 1970
var lev2 = performance.getEntriesByType("navigation")[0]; //ms since page started to load
var nav = lev2 || lev1; //if lev2 is undefined then use lev1
if (!nav[eventName] && fallback) eventName = fallback
// approximate t microseconds it takes to execute performance.now()
var i = 10000, t = performance.now()
while(--i) performance.now()
t = (performance.now() - t)/10000 // < 10 microseconds
var oldTime = new Date().getTime(),
newTime = performance.now(),
timeOrigin = performance.timeOrigin?
performance.timeOrigin:
oldTime - newTime - t; // approximate
return nav[eventName] - (lev2? 0: timeOrigin);
// return nav[eventName] - (lev2? 0: lev1.navigationStart); //alternative?
}
The performance.timeOrigin is reduced in the case where old timing lev1 is used.
If browser does not have it then approximate timeOrigin by reducing performance.now() the time since timeOrigin, from (new Date().getTime()) the time since Unix Epoch to result in the time to timeOrigin since Unix Epoch. Apparently it is the definition though the link was a bit vague about it. I confirmed by testing and I trust the answer. Hopefully w3c have a better definition of timeOrigin than: the high resolution timestamp of the start time of the performance measurement.
The functions returned value represents the time elapsed since the time origin.
It may be insignificant in most cases, but the measured time t it took to execute performance.now() is removed to approximate simultaneous execution.
I measured t to almost 10 microseconds on my Raspberry Pi that was fairly stable with various loop sizes. But my Lenovo was not as precise rounding off decimals and getting shorter times on t when tested bigger loop sizes.
An alternative solution is commented away in the last line of code.
The deprecated performance.timing.navigationStart:
representing the moment, in miliseconds since the UNIX epoch, right
after the prompt for unload terminates on the previous document in the
same browsing context. If there is no previous document, this value
will be the same as PerformanceTiming.fetchStart
So, to check current document (ignoring any previous) then use the deprecated performance.timing.fetchStart:
representing the moment, in miliseconds since the UNIX epoch, the
browser is ready to fetch the document using an HTTP request. This
moment is before the check to any application cache.
It is of course correct to use a deprecated property if it is the only one the browser understand. It is used when "navigation" is not defined in the getEntriesByType otherwise having good browser support.
A quick check confirmed each other by this line just before return:
console.log(performance.timeOrigin + '\n' + lev1.navigationStart + '\n' + lev1.fetchStart)
With a result that looks like this in my Chrome
1560807558225.1611
1560807558225
1560807558241
It is only possible if the browser supports HR time 2:
let unixTime = hrTime + performance.timeOrigin;
let hrTime = unixTime - performance.timeOrigin;
However, performance is generally used for time diffs, which do not care what the origin of absolute timestamps is.
For the browsers that do not support HR time 2, or those that "support" it half-heartedly, you can fake it this way:
const hrSyncPoint = performance.now();
const unixSyncPoint = new Date().getTime();
const timeOrigin = unixSyncPoint - hrSyncPoint;
It's not super-exact, but should be good enough for most purposes (on my system, performance.timeOrigin - timeOrigin is sub-millisecond).

Is there a better solution than setInterval when I need the interval to be very precise over time?

I'm building a browser game similar to Guitar Hero. The way it works is setInterval is set to execute every 16th note depending on the tempo, which is usually 100-150 milliseconds. The function it executes just checks if there's a note that needs to be checked against the user's keypresses.
But I've been reading about how setInterval can suffer from drift and may not be very accurate. For a song that lasts 3-4 minutes and needs 100ms precision (if the timing mechanics are even slightly off, that might be very frustrating for the users), it seems like it may not be the greatest solution.
So is there an alternative that's more precise? T
It probably would be a better idea to calculate everything in absolute time. So have a var starttime = Date.now();, and then calculate where every note should be var expected_note_time = starttime+beat_interval*beat_number. You can then add a listener on keypress and then log the exact time the keypress was hit. If abs(time_pressed-expected_note_time) < ALLOWED_MISTAKE_VARIANCE, then add that point to the user's score. Good luck.
Agree, setInterval have some issues, like it doesn't care whether the callback is still running or not and isn't that flexible.
you can implement you own method something like this :
function interval(func, wait, times){
var interv = function(w, t){
return function(){
if(typeof t === "undefined" || t-- > 0){
setTimeout(interv, w);
try{
func.call(null);
}
catch(e){
t = 0;
throw e.toString();
}
}
};
}(wait, times);
setTimeout(interv, wait);
};
this function has an internal function called interv which gets invoked automatically via setTimeout, within interv is a closure that checks the the repetition times, invokes the callback and invokes interv again via setTimeout. In case an exception bubbles up from the callback call the interval calling will stop and the exception will be thrown.
you can use it like this :
interval(function(){
// Code block goes here
}, 1000, 10);
which execute a piece of code 5 times with an interval or 10 seconds and you can do something in between.
You could cache the ticks at the start of the song, get the number of ticks since then, see if a note exists at that point.
Return the number of milliseconds since 1970/01/01:
var d = new Date();
var n = d.getTime();
The result of n could be:
1502156888172
From: https://www.w3schools.com/jsref/jsref_gettime.asp
and needs 100ms precision
The setInterval() function will drift because of the way javascript is built (event loop) and if you block it with a heavy CPU intensive task, it will delay. However, the drift will be very small (less that a ms) if you do it correctly.
A good way to avoid that would be to use multiple thread, but that is not easily achieved in JavaScript.

The javascript timing resolution in my browsers seems to be ~8ms. How can I increase it? [duplicate]

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?
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?
No and no. You're not going to get anything close to a perfectly accurate timer with setTimeout() - browsers aren't set up for that. However, you don't need to rely on it for timing things either. Most animation libraries figured this out years ago: you set up a callback with setTimeout(), but determine what needs to be done based on the value of (new Date()).milliseconds (or equivalent). This allows you to take advantage of more reliable timer support in newer browsers, while still behaving appropriately on older browsers.
It also allows you to avoid using too many timers! This is important: each timer is a callback. Each callback executes JS code. While JS code is executing, browser events - including other callbacks - are delayed or dropped. When the callback finishes, additional callbacks must compete with other browser events for a chance to execute. Therefore, one timer that handles all pending tasks for that interval will perform better than two timers with coinciding intervals, and (for short timeouts) better than two timers with overlapping timeouts!
Summary: stop using setTimeout() to implement "one timer / one task" designs, and use the real-time clock to smooth out UI actions.
.
REF; http://www.sitepoint.com/creating-accurate-timers-in-javascript/
This site bailed me out on a major scale.
You can use the system clock to compensate for timer inaccuracy. If you run a timing function as a series of setTimeout calls — each instance calling the next — then all you have to do to keep it accurate is work out exactly how inaccurate it is, and subtract that difference from the next iteration:
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);
This method will minimize drift and reduce the inaccuracies by more than 90%.
It fixed my issues, hope it helps
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/
If you need to get an accurate callback on a given interval, this gist may help you:
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's answer is pretty much what I'd say, although I'd add the following about UI animation/events:
If you've got a box that's supposed to slide onto the screen, expand downwards, then fade in its contents, don't try to make all three events separate with delays timed to make them fire one after another - use callbacks, so once the first event is done sliding it calls the expander, once that's done it calls the fader. jQuery can do it easily, and I'm sure other libraries can as well.
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.
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.
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);
}
}
This is a timer I made for a music project of mine which does this thing. Timer that is accurate on all devices.
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}
Hate to say it, but I don't think there is a way to alleviate this. I do think that it depends on the client system, though, so a faster javascript engine or machine may make it slightly more accurate.
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. ...
There is definitely a limitation here. To give you some perspective, the Chrome browser Google just released is fast enough that it can execute setTimeout(function() {}, 0) in 15-20 ms whereas older Javascript engines took hundreds of milliseconds to execute that function. Although setTimeout uses milliseconds, no javascript virtual machine at this point in time can execute code with that precision.
Dan, from my experience (that includes implementation of SMIL2.1 language in JavaScript, where time management is in subject) I can assure you that you actually never need high precision of setTimeout or setInterval.
What does however matter is the order in which setTimeout/setInterval gets executed when queued - and that always works perfectly.
JavaScript timeouts have a defacto limit of 10-15ms (I'm not sure what you're doing to get 200ms, unless you're doing 185ms of actual js execution). This is due to windows having a standard timer resolution of 15ms, the only way to do better is to use Windows' higher resolution timers which is a system wide setting so can screw with other applications on the system and also chews battery life (Chrome has a bug from Intel on this issue).
The defacto standard of 10-15ms is due to people using 0ms timeouts on websites but then coding in a way that assumes that assumes a 10-15ms timeout (eg. js games which assume 60fps but ask 0ms/frame with no delta logic so the game/site/animation goes a few orders of magnitude faster than intended). To account for that, even on platforms that don't have windows' timer problems, the browsers limit timer resolution to 10ms.
Here are what I use. Since it's JavaScript, I will post both my Frontend and node.js solutions:
For both, I use the same decimal rounding function that I highly recommend you keep at arms length because reasons:
const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)
places - Number of decimal places at which to round, this should be safe and should avoid any issues with floats (some numbers like 1.0000000000005~ can be problematic). I Spent time researching the best way to round decimals provided by high-resolution timers converted to milliseconds.
that + symbol - It is a unary operator that converts an operand into a number, virtually identical to Number()
Browser
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')
You could consider using the html5 webaudio clock which uses the system time for better accuracy

Stopwatch not working, it's going way too faster

As I was looking for a simple Stopwatch implementation in JS, I found this code http://codepen.io/_Billy_Brown/pen/dbJeh/
The problem is that it's not working fine, the clock go way too fast. I got 30 seconds on the screen when i got only 23 seconds on my watch.
And I don't understand why. The timer function is called every millisecond and should be updating the time correctly.
setInterval(this.timer, 1);
Is the problem coming from the browser or from the JS code.
Thanks in advance.
The timers in Javascript doesn't have millisecond precision.
There is a minimum time for the interval, which differs depending on the browser and browser version. Typical minimums are 4 ms for recent browsers and 10 ms for a little older browsers.
Also, you can't rely on the callback being called at exact the time that you specify. Javascript is single threaded, which means that if some other code is running when the timer triggers a tick, it has to wait until that other code finishes.
In fact the code you gave is imitating time flow, but it is not synchronized with system time.
Every millisecond it just invokes the function this.time, which performs recounting of millis, seconds and so on
without getting native system time, but just adding 1 to variable representing "imaginary milliseconds".
So we can say that resulting pseudo-time you see depends on your CPU, browser and who knows what else.
On our modern fantastically fast computers the body of this.time function is being executed faster than millisecond (wondering what would happen on Pentium 2 with IE5 on board).
Anyhow there is no chance for the this.time to be executed exactly in particular fixed period on all computers and browsers.
The simplest correct way to show the time passed since startpoint according to the system time is:
<body>
<script>
var startTime = new Date() // assume this initialization to be start point
function getTimeSinceStart()
{
var millisSinceStart = new Date() - startTime
, millis = millisSinceStart % 1000
, seconds = Math.floor(millisSinceStart / 1000)
return [seconds, millis].join( ':' )
}
(function loop()
{
document.title = getTimeSinceStart() // look on the top of page
setTimeout( loop, 10 )
}())
</script>
</body>
P.S. What #Guffa says in his answer is correct (generally for js in browsers), but in this case it does not matter and not affect the problem

Safari and requestAnimationFrame gets DOMHighResTimestamp; window.performance not available

I created an animation using requestAnimationFrame. Works fine in Windows Chrome and IE; Safari (tested Safari 6 and 7) breaks. It turns out that rAF get a DOMHighResTimestamp rather than a Date timestamp. That's all fine and good, and what I expected, as it's now part of the spec. However, as far as I've been able to find, there's no way to get the current DOMHighResTimestamp (i.e. window.performance is not available, even with a prefix). So if I create the start time as a Date timestamp, it behaves radically wrong when I try to determine progress within the rAF callback (very small negative numbers).
If you look at this JSBin on Safari, it won't animate at all.
In this JBin, I've made a change to "skip" the first frame (where the time parameter is undefined), so that startTime gets set to the time parameter on the next frame. Seems to work, but skipping a frame seems a bit crappy.
Is there some way to get the current DOMHighResTimestamp in Safari, given the lack of window.performance? Or alternately, force rAF into some sort of legacy mode that forces it to get a Date timestamp instead?
Does anyone know why Safari has this inconsistency, where it provides the parameter in a format that you can't get at any other way?
Performance.now() is only a recommendation as of now. https://developer.mozilla.org/en-US/docs/Web/API/Performance.now() I can only assume it's a matter of time before it's official, seeing as how everyone but Safari has it built in.
Besides that fact use this to your advantage. Since you know requestAnimationFrame returns a DOMHighResTimestamp use that for your timing.
Game.start = function(timestamp){
if(timestamp){
this.time = timestamp;
requestAnimationFrame(Game.loop);
}else{
requestAnimationFrame(Game.start);
}
}
Game.loop = function(timestamp){
Game.tick(timestamp);
... your code
requestAnimationFrame(Game.loop);
}
Game.tick = function(timestamp) {
this.delta = timestamp - this.time;
this.time = timestamp;
};
What I do here, is call Game.start which will begin the loop (I've run into situations where the timestamp was undefined so we try until we get something valid). Once we get that we have our base time and since RAF is going to return a timestamp our tick function never has to call Date.now or performance.now as long as we pass it the timestamp returned by requestAnimationFrame.

Categories

Resources