Safari and requestAnimationFrame gets DOMHighResTimestamp; window.performance not available - javascript

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.

Related

Sound fades out, but does not fade in -- why?

I think I understand the main concept of Web Audio API, as well as how sounds are working in general. And even though I managed to make the sound "fade out", I cannot figure out, why it is not "fading in" in the following snippet I wrote to represent the problem:
(function ()
{
'use strict';
var context = new AudioContext(),
wave = context.createOscillator(),
gain = context.createGain(),
ZERO = 0.000001;
wave.connect(gain);
gain.connect(context.destination);
wave.type = 'sine';
wave.frequency.value = 200;
gain.gain.value = ZERO;
wave.start(context.currentTime);
gain.gain.exponentialRampToValueAtTime(1.00, 1.0);
gain.gain.exponentialRampToValueAtTime(ZERO, 3.0);
})();
NOTE: The same problem appeared on Firefox (Linux) and Chrome (Windows) too
Replacing your gain.gain.value = ZERO line with:
gain.gain.setValueAtTime(ZERO, 0);
will fix the problem.
The rationale is in the actual specification of the exponentialRampToValueAtTime() function:
Schedules an exponential continuous change in parameter value from the previous scheduled parameter value to the given value
So, if there's no previous scheduled parameter value (only a fixed value) then the function cannot interpolate. The same applies to the linearRampToValueAtTime function.
This may also be useful from the MDN documentation:
AudioParam.value ... Though it can be set, any modifications happening while there are automation events scheduled — that is events scheduled using the methods of the AudioParam — are ignored, without raising any exception
You need to
gain.gain.setValueAtTime(ZERO, 0);
because just setting
gain.gain.value = ZERO;
does not set a schedule point in the AudioParam scheduler - so it's scheduling from the last know schedule point (which is the default value of 1 at time=0). Mixing setting .value and scheduling does not tend to work well; I've had an article 75% written about this for a long time, and just haven't gotten it released.

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

WebKit browsers slow to update canvas

I've created a simple binary clock in javascript, using Canvas, and in Chrome/Safari even though I'm drawing to the canvas many times every second it only updates the screen exactly once per second.
FireFox is updating the instant I draw to the canvas, and I think my code is efficient (activity monitor says the browser is using 5% to 10% of a single CPU core while the animation is running).
How can I make webkit browsers update more often? My actual code is at jsfiddle:
http://jsfiddle.net/mqGKR/
But basically this is what I'm doing:
function updateCanvas()
{
if (!canvasNeedsUpdating()) {
return;
}
var ctx = blockView.getContext("2d");
ctx.clearRect(0, 0, width, height);
if (canvasNeedsFill()) {
ctx.fillStyle = "rgba(255,255,255,1.0)";
ctx.fillRect(0, 0, width, height);
}
window.setTimeout(updateCanvas, 10);
}
Wow. This gets weird.
This has nothing to do with canvas. It has to do with your BinaryTime class. There's some difference in the functioning of the Date objects between at least Chrome and Firefox.
The beginning and end are 1370318400000 and 1370404800000 in FireFox. Every time. Presumably this is what you want looking at the comments.
They are changing every single time in Chrome, which means they are definitely not representing midnight this morning and midnight tonight as your comments suggest.
In other words, the Date object in Chrome/webkit appears broken. But it's more accurate. It's less accurate in Firefox in a more subtle way, but for now lets focus on a fix. (Later tonight, while crying into a tub of ice cream, I'll submit some bug reports).
But Chrome is doing the right thing here, because you are not calling setMilliseconds and chrome is respecting that. Firefox gets weird and does the wrong thing, but it just so happens to be what you want.
So anyway, the easy way that works off the bat is to use setHours with all four arguments:
// init "beginning" timestamp as midnight this morning
var beginning = new Date();
beginning.setHours(0, 0, 0, 0);
beginning = beginning.getTime();
// init "end" timestamp as as midnight tonight
var end = new Date(date);
end.setHours(0, 0, 0, 0);
end.setDate(end.getDate() + 1);
end = end.getTime();
I'd just do that for now. Working example:
http://jsfiddle.net/wvR6H/
The slightly-more-drawn-out problem is that in Chrome/WebKit you need to also set the milliseconds:
blah.setMilliseconds(0);
You need to set it in FireFox too, you're exploiting a sort-of Firefox bug as your code is written right now. It would be "broken" in Firefox too if you had beginning = new Date(), in other words with an empty constructor. See here for instance: http://jsfiddle.net/VbWnk/
It just so happens that new Date(new Date()) in Firefox trims off the milliseconds for you. Actually, to be fair, IE works the same way so Chrome/Webkit is the odd one out. The ECMAScript specification is not clear on who's right (FF/IE seem to be right, but talk for EcmaScript 6 indicate they may special case new Date(Date). A Date object is not technically an acceptable argument to the Date constructor, but a string is, and the Date string does not contain milliseconds. This suggests FireFox/IE are more correct, but WebKit's way is understandable too, and may be right in the future.
...But anyway, setHours(a,b,c,d) sets the hours, minutes, seconds, milliseconds as shorthand, so that's easier to write.
Hope your project goes well.

requestAnimationFrame is passing unexpected parameters in IE10

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.

Better way of getting time in milliseconds in javascript?

Is there an alternative in JavaScript of getting time in milliseconds using the date object, or at least a way to reuse that object, without having to instantiate a new object every time I need to get this value? I am asking this because I am trying to make a simple game engine in JavaScript, and when calculating the "delta frame time", I have to create a new Date object every frame. While I am not too worried about the performance implications of this, I am having some problems with the reliability of the exact time returned by this object.
I get some strange "jumping" in the animation, every second or so, and I am not sure if this is related to JavaScript's Garbage Collection or a limitation of the Date object when updating so fast. If I set the delta value to some constant, then the animation if perfectly smooth, so I am fairly sure this "jumping" is related to the way I get the time.
The only relevant code I can give is the way I calculate the delta time :
prevTime = curTime;
curTime = (new Date()).getTime();
deltaTime = curTime - prevTime;
When calculating movement / animation I multiply a constant value with the delta time.
If there is no way to avoid getting the time in milliseconds by using the Date object, would a function that increments a variable (being the elapsed time in milliseconds since the game started), and which is called using the SetTimer function at a rate of once every milliseconds be an efficient and reliable alternative?
Edit : I have tested now my code in different browsers and it seems that this "jump" is really only apparent in Chrome, not in Firefox. But it would still be nice if there were a method that worked in both browsers.
Try Date.now().
The skipping is most likely due to garbage collection. Typically garbage collection can be avoided by reusing variables as much as possible, but I can't say specifically what methods you can use to reduce garbage collection pauses.
As far that I know you only can get time with Date.
Date.now is the solution but is not available everywhere : https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/now.
var currentTime = +new Date();
This gives you the current time in milliseconds.
For your jumps. If you compute interpolations correctly according to the delta frame time and you don't have some rounding number error, I bet for the garbage collector (GC).
If there is a lot of created temporary object in your loop, garbage collection has to lock the thread to make some cleanup and memory re-organization.
With Chrome you can see how much time the GC is spending in the Timeline panel.
EDIT: Since my answer, Date.now() should be considered as the best option as it is supported everywhere and on IE >= 9.
I know this is a pretty old thread, but to keep things up to date and more relevant, you can use the more accurate performance.now() functionality to get finer grain timing in javascript.
window.performance = window.performance || {};
performance.now = (function() {
return performance.now ||
performance.mozNow ||
performance.msNow ||
performance.oNow ||
performance.webkitNow ||
Date.now /*none found - fallback to browser default */
})();
If you have date object like
var date = new Date('2017/12/03');
then there is inbuilt method in javascript for getting date in milliseconds format which is valueOf()
date.valueOf(); //1512239400000 in milliseconds format
This is a very old question - but still for reference if others are looking at it - requestAnimationFrame() is the right way to handle animation in modern browsers:
UPDATE: The mozilla link shows how to do this - I didn't feel like repeating the text behind the link ;)

Categories

Resources