I want to make a JavaScript animation take 5 seconds to complete using requestAnimationFrame().
I don't want a strict and precise timing, so anything close to 5 seconds is OK and I want my code to be simple and readable, so solutions like this won't work for me.
My question is, is it safe to assume most browsers render the page at 60 fps? i.e. if I want my animation to take 5 seconds to complete, I'll divide it to 60 * 5 = 300 steps and with each call of function draw() using requestAnimationFrame(), draw the next step of animation. (Given the fact the animation is pretty simple, just moving a colored div around.)
By the way, I can't use jQuery.
Edit: Let me rephrase the question this way: Do all browsers 'normally' try to render the page at 60 fps? I want to know if Chrome for example renders at 75 fps or Firefox renders at 70 fps.
(Normal condition: CPU isn't highly loaded, RAM is not full, there are no storage failures, room is properly ventilated and nobody tries to throw my laptop out the window.)
Relying on 60 frames per second is very unsafe, because the browser isn't always in the same conditions, and even if it tries to render the page at the maximum fps possible, there's always a chance of the processor/cpu/gpu being busy doing something else, causing the FPS to drop down.
If you want to rely on FPS (although I wouldn't suggest you so), you should first detect the current fps, and adjust the speed of your animation frame per frame. Here's an example:
var lastCall, fps;
function detectFps() {
var delta;
if (lastCall) {
delta = (Date.now() - lastCall)/1000;
lastCall = Date.now();
fps = 1/delta;
} else {
lastCall = Date.now();
fps = 0;
}
}
function myFunc() {
detectFps();
// Calculate the speed using the var fps
// Animate...
requestAnimationFrame(myFunc);
}
detectFps(); // Initialize fps
requestAnimationFrame(myFunc); // Start your animation
It depends on the GPU and monitor combination. I have a good GPU and a 120 hertz monitor, so it renders at 120 fps. During the render, If I move to 60 hertz monitor, it will max out at 60 fps.
Another factor, that happens in some browsers/OS, is the iGPU being used instead of the discrete gpu.
As already stated by others, it isn't.
But if you need to end your animation in approximately 5 seconds and it's not crucial not to miss any frames in the animation, you can use the old setTimeout() way. That way you can miss a target by a few milliseconds, and some of the frames in your animation will be skipped (not rendered) because of the fps mismatch, but this can be a "good enough" solution, especially if your animation is simple as you state it is, there's a chance that users won't even see the glitch.
It's not safe to assume everyone can handle animation.
People will have different needs.
A lot of common animations, and common web design practices, give me awful migraines, so I set my browser to 1 frame per second to kill the animation without causing too much fast flashing.
Related
Chrome/Firefox are deciding to fire requestAnimationFrame at 30FPS. The profiler shows most of the time is spent idling, so its not cpu cycles being burned up.
I tried writing a delta timing to "catch up" / stay synced. The FPS is correct according to my FPS meter, but the result is very choppy animating. I also tried Drawing immediately after each Update, still choppy.
I am running a very vanilla rAF paradigm and notice the window size affects the FPS, but the profiler doesn't seem to explain why the FPS drops at full screen
let start = null;
let step = (timestamp)=>{
if(thisTimerID !== scope._timerID){
return;
}
start = start || timestamp + delay;
let current = timestamp;
let delta = current - start;
if(delta >= delay) {
this.Update();
this.Draw();
start = timestamp;
}
requestAnimationFrame(step);
};
thisTimerID = this._timerID = requestAnimationFrame(step);
Should I try requestAnimationFrame, and if I detect a low FPS when initializing, fall back to setInterval? Should I just use requestAnimationFrame anyway? Maybe there's a flaw in my delta timing logic? I know theres a huge flaw because if you switch tabs then come back, it will try to catch up and might think it missed thousands of frames!
Thank you
EDIT:
requestAnimationFrame IS firing at 60fps under minimal load. It is the Draw method that is causing the frame rate to drop. But why? I have 2 profiles showing a smaller resolution runs at 60fps, full screen does not.
60FPS 350x321 Resolution:
38FPS 1680x921 Resolution:
What's really strange to me, is the full screen version is actually doing LESS work according to the profiler.
I'm animating transition of 36 000 colours in 18 seconds using JavaScript. When user presses a button, he should be notified on what colour the animation was at the point when the button has been clicked. However, JavaScript measures time in milliseconds, which basically means that if the user presses the button on t=10ms the animation will be on colour 20 and when he presses it on t=11ms the animation will already be on 22.
Is there a way to measure time in JavaScript more accurately? So for example, I can be able to tell that the button was pressed on t=10.5ms so the animation would be on colour 21.
The newer browser versions support performance.now which gives time
measured in milliseconds, accurate to one thousandth of a millisecond.
performance.now uses DomHighResTimeStamp as the time value which has the following property
The unit is milliseconds and should be accurate to 5 µs (microseconds).
var t1 = performance.now();
var t2 = performance.now();
console.log('passed ' + (t2 - t1) * 1000.0 + ' microseconds');
A polyfill can be found here and the list of the supported browsers can be found here.
Here's a good article on performance.now by Paul Irish
One thing to note here is that the browser usually renders the screen at 60 frames per second, or once every ~16.67 milliseconds, irrelevant of the page performing animations or not. This means that what you're trying to do probably isn't possible since, although JS code can run in less than a millisecond, animations will always be separated by at least those 16.67ms. Thus every ~32nd color will actually be displayed because you're trying to display 2 colors per 1ms.
To update the colors at the frame rate of your browser, use requestAnimationFrame.
Note: The 60fps is most common but the browser will adjust to the refresh rate of the screen.
I'm trying to make an animation where the frame rate is below 16ms. For many of the frames I get this pattern:
Where the majority of the frame is empty. The yellow is my requestAnimationFrame script, then rendering and painting. But why, when this only takes a small fraction of the frame time, is the frame still nearly 20ms long?
Here's some code that demonstrates it:
var square = document.getElementById('square');
document.addEventListener('mousemove', function(e) {
square.style.top = e.clientY - 25 + 'px';
square.style.left = e.clientX - 25 + 'px';
});
Fiddle: https://jsfiddle.net/5peLpf53/
Screenshot of timeline from fiddle:
Most, if not all, browsers run at 60 fps maximum. This is the same as once every 16.6 ms. There is no need for any higher, since many monitors are not higher than 60 Hz.
requestAnimationFrame just tells the browser to run some code before the next repaint of the screen. You can't update the screen any faster than this.
If you have some logic that requires more frequent update, then you should put it somewhere else.
On my application, I have a canvas which CSS size (CSS, not html) updates depending on the window size.
I have a main gameplayLoop which looks like this :
run = function(){
console.log(timerDiff(frameTime));
game.inputManage();
game.logics();
game.graphics.animate();
game.graphics.render();
frameTime = timerInit();
requestAnimFrame(run);
}
My requestAnimFrame function is the one from Paul Irish :
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
So basically the problem is that the timer that I log which measures the time between the requestAnimFrame call and the effective function call totally changes.
If my browser is in fullscreen I get like 50/60 ms, and if I have a small window I can get to something like 10 ms.
As the requestAnimFrame call is supposed to call constantly the function under a 60fps rythm (which is something like 30 ms I think) I shouldn't have this kind of result, since there is basically nothing happening between the timer's creation and its check, except the requestAnimFrame
I also have some recurring micro-freezes (less than a second) which happens like every 2/3 seconds. But the timer doesn't detect any change in the time (like even the time counter of javascript is blocked)
My timers functions are like this but it doesn't really matter here
timerInit = function()
{
return new Date();
}
timerDiff = function(datePrev)
{
return new Date().getTime() - datePrev.getTime();
}
Well, the standard basically says that requestAnimationFrame will "do its best" to run at a "consistent" frame rate. That doesn't guarantee a 60fps; it just states that it will animate as fast as it can.
My experience with it has been as dim as yours so far unfortunately. I ended up going back to setTimeout. It goes at about the same rate, and graphical updates are accurate and don't skip a beat as they did with requestAnimationFrame. Sure it wasn't 60fps, but at least you could tell what was going on.
I'm sure the performance will only improve as browser developers optimize the new function, but for the time being, consider going back to timeouts.
Edit: I would like to remind people that this was answered a year ago, and the times have changed :)
"So basically the problem is that the timer that I log which measures the time between the requestAnimFrame call and the effective function call totally changes..."
Of course it does, that's why you need to measure it and BASED ON THIS TIME DIFFERENCE VALUE you calculate your next frame.
Let's assume you want to animate the 'left' css property of a div from 0px to 120px in 1 second with 60 fps.
using setTimeout, because YOU set the number of frames, you know how much you must increment the 'left' property: 120px/60frames = 2px/frame
using requestAnimationFrame, you have no ideea when the next frame will happen, until it happens; then you measure the time difference between this and the previous frame an you calculate the value you must increment the css 'left value' with; if the frame happens after 500ms, increment distance = (120px * 500ms)/1000ms = 60px; the ideea is that at the start of the animation you can't have a fixed, predetermined value to increment with at each frame, you need to calculate it dynamically based on the time difference
So even if the animation won't be at the advertised 60fps, if frames are skipped, every frame that is not skipped will draw the accurate updated situation.
You have to fiddle a bit to decide when the animation should end.
How can I tell if the canvas's slow performance is caused by the drawing itself, or the underlying logic that calculates what should be drawn and where?
The second part of my question is: how to calculate canvas fps? Here's how I did it, seems logical to me, but I can be absolutely wrong too. Is this the right way to do it?
var fps = 0;
setInterval(draw, 1000/30);
setInterval(checkFps, 1000);
function draw() {
//...
fps++;
}
function checkFps() {
$("#fps").html(fps);
fps = 0;
}
Edit:
I replaced the above with the following, according to Nathan's comments:
var lastTimeStamp = new Date().getTime();
function draw() {
//...
var now = new Date().getTime();
$("#fps").html(Math.floor(1000/(now - lastTimeStamp)));
lastTimeStamp = now;
}
So how's this one? You could also calculate only the difference in ms since the last update, performance differences can be seen that way too. By the way, I also did a side-by-side comparison of the two, and they usually moved pretty much together (a difference of 2 at most), however, the latter one had bigger spikes, when performance was extraordinarily low.
Your FPS code is definitely wrong
setInterval(checkFps, 1000);
No-one assures this function will be called exactly every second (it could be more than 1000ms, or less - but probably more), so
function checkFps() {
$("#fps").html(fps);
fps = 0;
}
is wrong (if fps is 32 at that moment it is possible that you have 32 frames in 1.5s (extreme case))
beter is to see what was the real time passes since the last update and calculate the realtimepassed / frames (I'm sure javascript has function to get the time, but I'm not sure if it will be accurate enough = ms or better)
fps is btw not a good name, it contains the number of frames (since last update), not the number of frames per second, so frames would be a better name.
In the same way
setInterval(draw, 1000/30);
is wrong, since you want to achieve a FPS of 30, but since the setInterval is not very accurate (and is probably going to wait longer than you say, you will end up with lower FPS even if the CPU is able to handle the load)
Webkit and Firebug both provide profiling tools to see where CPU cycles are being spent in your javascript code. I'd recommend starting there.
For the FPS calculation, I don't think your code is going to work, but I don't have any good recommendation :(
Reason being: Most (all?) browsers use a dedicated thread for running javascript and a different thread for running UI updates. If the Javascript thread is busy, the UI thread won't be triggered.
So, you can run some javascript looping code that'll "update" the UI 1000 times in succession (for instance, setting the color of some text) - but unless you add a setTimeout to allow the UI thread to paint the change, you won't see any changes until the 1000 iterations are finished.
That said, I don't know if you can assertively increment your fps counter at the end of the draw() routine. Sure, your javascript function has finished, but did the browser actually draw?
Check if you dont use some innerHTML method to debug your project. This can slow your project in a way you can't imagine, especially if you do some concatenation like this innerHTML += newDebugValues;
Or like desau said, profile your cpu usage with firebug or webkit inner debugger.