How to solve different FPS in requestAnimationFrame on different browsers?
How to solve different FPS in requestAnimationFrame
on different browsers?
THREE.js
that uses requestAnimationFrame
and it is fast on Google Chrome 15.
However, it is really slow on Firefox 6 and really really slow (slower than Firefox) on IE9.
This is re开发者_JAVA技巧ally a big problem and I am wondering if there is a solution to that.
Thanks.
The common thing to do is to create a deltaTime (dt) variable which is then be used as a parameter for every animation/update cycle.
Code is only for visualizing the problem/solution.
// ...
timer: function(){
var now = new Date().getTime(); // get current time
this.controls.dt = now - this.controls.time; // calculate time since last call
this.controls.time = now; // update the current application time
this.controls.frame++; // also we have a new frame
return this.controls.dt ;
}
for any call to the render function you then pass dt
// we call the update function with every request frame
update: function(){
var dt = this.timer();
_.each(this.activeViews, function(item){ item.update(dt); }); // this is underscore.js syntax
}
item.update(dt) looks like that
//...
var x = this.position.get(x);
x = x + (10*dt); // meaning: x increases 10 units every ms.
this.position.x = x;
As far as I know there's no way to really fix this, other than making your code less resource intensive.
Chrome seems to be the fastest browser, but usually FF is not far behind, but IE is still slow. Depending on the rendering methods, canvas, svg or webGL, it's also very dependent on your local hardware as it uses the clientside for most things, and complicated webGL renderings need a powerful GPU to achieve good framerates.
There are ways to measure the framerate on the fly, and change your animations accordingly.
Here's a very simple example that measures framerate.
function step(timestamp) {
var time2 = new Date;
var fps = 1000 / (time2 - time);
time = time2;
document.getElementById('test').innerHTML = fps;
window.requestAnimationFrame(step);
}
var time = new Date(), i = 0;
window.requestAnimationFrame(step);
<div id="test"></div>
This is just an instant measure that's not that accurate, you'd probably want something that measures over some time to get a more correct framerate for the browser being used.
Also note the timestamp
argument, which in requestAnimationFrame
is high-res timestamp with a minimal precision of 1 milliseconds, that can also be used to deterine the speed of the animation, and any browser lag.
On some browsers requestAnimationFrame works something like
setTimeout(callback, 1000 / (16 + N)
where N is time required for your code to execute. Which means it caps your FPS at 62Hz but if your code works slowly, it will cap at something way lower. It basically tries to make a 16ms gap between every gap. Of course, this is not true for all browsers and will probably change in the future anyway but it still may give you an idea how it works.
Even if it was implemented the same in every browser, there are many factors which affect the performance of your code and etc. You can never be sure your code will be running at a constant frequency.
The Crafty framework does something that's a bit different, but might work for some cases -- the number of game ticks per draws is not constant. Rather, it notices when the framerate is falling behind some ideal target, and will cycle through multiple game ticks before performing the draw step. You can see the step function on github.
This works well so long as the game would be running smoothly. But if you try something more processor intensive, it can tend to exacerbate the situation, as it will prioritize game logic over animation.
In any case, it'll only work if the game logic and render logic are somewhat decoupled. (If they were completely decoupled you might be able to put them in completely separate loops.)
As adeneo mentioned, the requestAnimationFrame
callback is sent a timestamp argument. Here is a solution to measure the delta between requestAnimationFrame
events using that timestamp argument instead of creating a separate variable using the Date()
function (which performance.now()
may be a better solution anyhow).
This solution also includes a Start/Stop option to show why I am using a separate function to initialize the previousTimestamp at each start, and why I am setting a reqID value.
var reqID, previousTimestamp, output;
const raf = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
const caf = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
// This is run first to set the previousTimestamp variable with an initial value, and then call the rafLoop function.
const startStop = () => {
if ($('#start-stop').prop('checked')) {
reqID = raf(timestamp => {
previousTimestamp = timestamp;
reqID = raf(rafLoop);
});
}
else caf(reqID);
};
const rafLoop = timestamp => {
animation(timestamp - previousTimestamp);
previousTimestamp = timestamp;
reqID = raf(rafLoop);
};
// Create animation function based on delta timestamp between animation frames
const animation = millisesonds => {
output.html(millisesonds);
};
$(document).ready(() => {
output = $('#output');
$('#start-stop').change(startStop);
$('#start-stop').prop('checked', true).trigger('change');
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>requestAnimationFrame</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<label for="start-stop">Start/Stop: </label><input class="switch" type="checkbox" id="start-stop"><br>
<div id="output"></div>
</body>
See also https://codepen.io/sassano/pen/wvgxxMp for another sample with animation from which this snippet was derived.
精彩评论