Related
Im working with this part of code which one render simple loading bar
const smallSpinner = document.getElementById('spinner-small').getContext('2d');
let pointToFill = 4.72;
let cw = smallSpinner.canvas.width; //Return canvas width
let ch = smallSpinner.canvas.height; //Return canvas height
let diff;
let = fillSmallSpinner = (startingPointSmall = 0) => {
diff = ((startingPointSmall / 100) * Math.PI * 2 * 10);
smallSpinner.clearRect(0, 0, cw, ch);
smallSpinner.lineWidth = 5;
smallSpinner.strokeStyle = '#d40511';
/* smallSpinner.textAlign = 'center';
smallSpinner.font = "25px monospace";
smallSpinner.fillText(no + '%', 50, 55); */ //uncomment this if you need percent progress inside spinner
smallSpinner.beginPath();
smallSpinner.arc(50, 50, 40, pointToFill, diff / 10 + pointToFill);
smallSpinner.stroke();
if (startingPointSmall >= 100) {
clearTimeout(fill);
}
startingPointSmall++;
}
let small = setInterval(fillSmallSpinner, 50);
The point is that when "startingPointSmall" is defined like normal variable
let startingPointSmall = 0;
it works totaly fine but i want to make this a little bit more usable and pass the starting point as a function parameter. When i do this like this with predefined starting point on 0% it doesnt work. Can someone explain me how to fix this?
Every time that setInterval queues up a call to fillSmallSpinner it will receive its default parameter - over and over!
A more common pattern is to wrap the function in a way that preserves the desired variable's scope:
const startFiller(callback, interval = 50, start = 0) {
let current = startPoint;
let timer = setInterval(() => {
callback(current++);
if (current >= 100) {
clearTimeout(timer);
}
}, interval);
});
startFiller(fillSmallSpinner);
You would then remove any existing timer-related logic from your fillSmallSpinner function. This approach has the added benefit of Separation of Concerns - if you decide you want to use a different function to render your spinner it no longer needs to concern itself with timers.
I'm trying to create a realistic slot machine animation, where the "spin" (actually a looped translation) gradually slows to a stop at a specified slot image. With my current implementation, it does actually stop at the right image, but it does so by spinning past it, then reversing direction and finally landing at the correct spot.
displacement = initial_velocity*delta_time + 1/2*acceleration*delta_time^2
to derive
acceleration = (2*total_displacement - 2*initial_velocity*delta_time) / delta_time^2.
private getAnimationDisplacement(): number {
if(!this.spinInProgress) return 0
// Time
const totalDuration = this.machine.spinDuration // ms
const curTime = new Date()
const dt = curTime.getTime() - (this.lastFrameTime || this.spinInProgress.startTime).getTime() // ms
this.lastFrameTime = curTime
// Calculate total (final) displacement for current spin
const spinCount = 1
const spinOffset = spinCount * this.spinInProgress.slotFaces // number of faces
const faceOffset = this.spinInProgress.getFaceOffset().offset // pixels
const totalOffset = new FaceOffset(spinOffset + faceOffset) // pixels
const totalDisplacement = totalOffset.offset * this.getFaceHeight() // pixels
// Acceleration
const acceleration = (2*totalDisplacement - 2*INIT_VELOCITY * totalDuration) / Math.pow(totalDuration, 2) // pixels / ms^2
// Diplacement
this.displacement += this.velocity * dt
// Velocity
this.velocity += acceleration * dt
// Modulo by max displacement causes animation to loop
return this.displacement % this.maxDisplacement
}
I can't figure out how this standard equation of motion is somehow going the right direction, then reversing direction, to finally land at the correct spot. I'm trying to avoid the weird reverse animation. Any insights would be greatly appreciated.
I have a hero character in the middle of the screen and I want to spawn zombies all around him in random positions but some distance away from him. heroDistance defines this distance.
It does not matter if they are pushed outside the boundaries of the screen when they are spawned, they all come towards him. It would not matter if this did not happen, but it just seemed easier.
At the moment the random location of the zombie is created for the x axis with random(screenWidth) and y axis random(screenHeight), and those values are fed into the spawnLocation function that depending on where they are in relation to the hero are either increased or decreased to move they away.
My code seems far too verbose, even though I have worked really hard on it. Am I missing some obvious technique to make it simpler?
const state = {
options: {
numberOfZombies: 10,
},
characters: {
hero: {
xPosition: 150,
yPosition: 150,
},
},
};
const screenWidth = 400;
const screenHeight = 400;
const random = range => Math.floor(Math.random() * range);
function createZombies(state) {
const heroDistance = 10;
const spawnLocation = (zomPos, heroPos, axisLength) => {
return zomPos > heroPos
? zomPos + axisLength / heroDistance
: zomPos - axisLength / heroDistance;
};
for (let index = 0; index < state.options.numberOfZombies; index += 1) {
console.log({
xPosition: spawnLocation(
random(screenWidth),
state.characters.hero.xPosition,
screenWidth,
),
yPosition: spawnLocation(
random(screenHeight),
state.characters.hero.yPosition,
screenHeight,
),
});
}
}
createZombies(state);
Generate a random angle and radius, and then transform these values into Cartesian coordinates.
let theta = Math.random() * (2 * Math.PI)
let r = Math.random() * variationInR + minimumR
let zombieX = Math.cos(theta) * r + heroX
let zombieY = Math.sin(theta) * r + heroY
If you want these to be integers, then make them so. This generates zombies uniformly radially from the hero at least minimumR units away (Pythagorean distance). If you want to maintain the Manhattan distance behavior, then generate your dX and dY and add them to the hero's position.
According to several developers (link1, link2) the proper way to have a constant frame rate with requestAnimationFrame is to adjust the "last rendered" time within the game loop as follows:
function gameLoop() {
requestAnimationFrame(gameLoop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval); // This weird stuff
doGameUpdate(delta);
doGameRender();
}
}
Where interval is 1000/fps (i.e. 16.667ms).
The following line makes no sense to me:
then = now - (delta % interval);
Indeed if I try it I don't get smooth graphics at all but fast then slow depending on the CPU:
https://jsfiddle.net/6u82gpdn/
If I just let then = now (which makes sense) everything works smoothly:
https://jsfiddle.net/4v302mt3/
Which way is "correct"? Or what are the tradeoffs I am missing?
Delta time is bad animation.
It seems just about anyone will post a blog about the right way to do this and that, and be totally wrong.
Both articles are flawed as they do not understand how requestAnimationFrame is called and how it should be used in relation to frame rate and time.
When you use delta time to correct animation positions via requestAnimationFrame you have already presented the frame, it's too late to correct it.
requestAnimationFrame's callback function is passed an argument that holds the high precision time in ms (1/1000th) accurate to microseconds (1/1,000,000th) second. You should use that time not the Date objects time.
The callback is called as soon as possible after the last frame was presented to the display, there is no consistency in the interval between calls to the callback.
Methods that use delta time need to predict when the next frame is presented so that object can be render at the correct position for the upcoming frame. If your frame rendering load is high and variable, you can not predict at the start of frame, when the next frame will be presented.
The rendered frame is always presented during the vertical display refresh and is always on a 1/60th second time. The time between frames will always be integers multiples of 1/60th giving only frame rates of 1/60, 1/30, 1/20, 1/15 and so on
When you exit the callback function the rendered content is held in the backbuffer until the next vertical display refresh. Only then is it moved to display RAM.
The frame rate (vertical refresh) is tied to the device hardware and is perfect.
If you exit the callback late, so that the browser does not have time to move the canvas content to the display, the back buffer is held until the next vertical refresh. Your next frame will not be called until after the buffer has been presented.
Slow renders do not reduce frame rate, they cause frame rate oscillations between 60/30 frames per second. See example snippet using mouse button to add render load and see dropped frames.
Use the time supplied to the callback.
There is only one time value you should use and that is the time passed by the browser to the requestAnimationFrame callback function
eg
function mainLoop(time){ // time in ms accurate to 1 micro second 1/1,000,000th second
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Post frame correction error.
Don't use delta time based animation unless you must. Just let the frames drop or you will introduce animation noise in the attempt to reduce it.
I call this post frame correction error (PFCE). You are attempting to correct a position in time for an upcoming and uncertain time, based on the past frame time, which may have been in error.
Each frame you are rendering will appear some time from now (hopefully in the next 1/60th second). If you base the position on the previouse rendered frame time and you dropped a frame and this frame is on time you will render the next frame ahead of time by one frame, and the same applies to the previous frame which would have been rendered a frame behind as a frame was skipped. Thus with only a frame dropped you render 2 frames out of time. A total of 3 bad frames rather than 1.
If you want better delta time, count frames via the following method.
var frameRate = 1000/60;
var lastFrame = 0;
var startTime;
function mainLoop(time){ // time in ms accurate to 1 micro second 1/1,000,000th second
var deltaTime = 0
if(startTime === undefined){
startTime = time;
}else{
const currentFrame = Math.round((time - startTime) / frameRate);
deltaTime = (currentFrame - lastFrame) * frameRate;
}
lastFrame = currentFrame;
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
This does not eliminate PFCE but is better than irregular interval time if you use delta time as timeNow - lastTime.
Frames are always presented at a constant rate, requestAnimationFrame will drop frames if it can not keep up, but it will never present mid frame. The frame rates will be at fixed intervals of 1/60, 1/30, 1/20, or 1/15 and so on. Using a delta time that does not match these rates will incorrectly position your animation.
A snapshot of animation request frames
This is a timeline of requestAnimationframe for a simple animation function. I have annotated the results to show when the callback is called. During this time the frame rate was constant at a perfect 60fps no frames where dropped.
Yet the times between callbacks is all over the place.
Frame render timing
The example shows frame timing. Running in SO sandbox is not the ideal solution and to get good results you should run this in a dedicated page.
What it shows (though hard to see for small pixels) is the various time error from ideal times.
Red is frame time error from the callback argument. It will be stable near 0ms from 1/60th second ideal frame time.
Yellow is the frame time error calculated using performance.now(). It varies about 2 ms in total with the occasional peek outside the range.
Cyan is the frame time error calculated using Date.now(). You can clearly see the aliasing due to the poor resolution of the date's ms accuracy
Green dots are the difference in time between the callback time argument and the time reported by performance.now() and on my systems is about 1-2ms out.
Magenta is the last frame's render time calculated using performance now. If you hold the mouse button you can add a load and see this value climb.
Green vertical lines indicate that a frame has been dropped / skipped
The dark blue and black background marks seconds.
The primary purpose of this demo is to show how frames are dropped as render load increase. Hold the mouse button down and the render load will start to increase.
When the frame time gets close to 16 ms you will start to see frames dropped. Until the render load reaches about 32ms you will get frames between 1/60 and 1/30, first more at 1/60th for every one at 1/30th.
This is very problematic if you use delta time and post frame correction as you will constantly be over and under correcting the animation position.
const ctx = canvas.getContext("2d");
canvas.width = 512;
canvas.height = 380;
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
var lastTime; // callback time
var lastPTime; // performance time
var lastDTime; // date time
var lastFrameRenderTime = 0; // Last frames render time
var renderLoadMs = 0; // When mouse button down this slowly adds a load to the render
var pTimeErrorTotal = 0;
var totalFrameTime = 0;
var totalFrameCount = 0;
var startTime;
var clearToY = 0;
const frameRate = 1000/60;
ctx.font = "14px arial";
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime; // global to this
ctx.clearRect(0,0,w,h);
const graph = (()=>{
var posx = 0;
const legendW = 30;
const posy = canvas.height - 266;
const w = canvas.width - legendW;
const range = 6;
const gridAt = 1;
const subGridAt = 0.2;
const graph = ctx.getImageData(0,0,1,256);
const graph32 = new Uint32Array(graph.data.buffer);
const graphClearA = new Uint32Array(ctx.getImageData(0,0,1,256).data.buffer);
const graphClearB = new Uint32Array(ctx.getImageData(0,0,1,256).data.buffer);
const graphClearGrid = new Uint32Array(ctx.getImageData(0,0,1,256).data.buffer);
const graphFrameDropped = ctx.getImageData(0,0,1,256);
const graphFrameDropped32 = new Uint32Array(graphFrameDropped.data.buffer);
graphClearA.fill(0xFF000000);
graphClearB.fill(0xFF440000);
graphClearGrid.fill(0xFF888888);
graphFrameDropped32.fill(0xFF008800);
const gridYCol = 0xFF444444; // ms marks
const gridYColMaj = 0xFF888888; // 4 ms marks
const centerCol = 0xFF00AAAA;
ctx.save();
ctx.fillStyle = "black";
ctx.textAlign = "right";
ctx.textBaseline = "middle";
ctx.font = "10px arial";
for(var i = -range; i < range; i += subGridAt){
var p = (i / range) * 128 + 128 | 0;
i = Number(i.toFixed(1));
graphFrameDropped32[p] = graphClearB[p] = graphClearA[p] = graphClearGrid[p] = i === 0 ? centerCol : (i % gridAt === 0) ? gridYColMaj : gridYCol;
if(i % gridAt === 0){
ctx.fillText(i + "ms",legendW - 2, p + posy);
ctx.fillText(i + "ms",legendW - 2, p + posy);
}
}
ctx.restore();
var lastFrame;
return {
step(frame){
if(lastFrame === undefined){
lastFrame = frame;
}else{
while(frame - lastFrame > 1){
if(frame - lastFrame > w){ lastFrame = frame - w - 1 }
lastFrame ++;
ctx.putImageData(graphFrameDropped,legendW + (posx++) % w, posy);
}
lastFrame = frame;
ctx.putImageData(graph,legendW + (posx++) % w, posy);
ctx.fillStyle = "red";
ctx.fillRect(legendW + posx % w,posy,1,256);
if((frame / 60 | 0) % 2){
graph32.set(graphClearA)
}else{
graph32.set(graphClearB)
}
}
},
mark(ms,col){
const p = (ms / range) * 128 + 128 | 0;
graph32[p] = col;
graph32[p+1] = col;
graph32[p-1] = col;
}
}
})();
function loop(time){
var pTime = performance.now();
var dTime = Date.now();
var frameTime = 0;
var framePTime = 0;
var frameDTime = 0;
if(lastTime !== undefined){
frameTime = time - lastTime;
framePTime = pTime - lastPTime;
frameDTime = dTime - lastDTime;
graph.mark(frameRate - framePTime,0xFF00FFFF);
graph.mark(frameRate - frameDTime,0xFFFFFF00);
graph.mark(frameRate - frameTime,0xFF0000FF);
graph.mark(time-pTime,0xFF00FF00);
graph.mark(lastFrameRenderTime,0xFFFF00FF);
pTimeErrorTotal += Math.abs(frameTime - framePTime);
totalFrameTime += frameTime;
totalFrameCount ++;
}else{
startTime = time;
}
lastPTime = pTime;
lastDTime = dTime;
lastTime = globalTime = time;
var atFrame = Math.round((time -startTime) / frameRate);
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,w,clearToY);
ctx.fillStyle = "black";
var y = 0;
var step = 16;
ctx.fillText("Frame time : " + frameTime.toFixed(3)+"ms",10,y += step);
ctx.fillText("Rendered frames : " + totalFrameCount,10,y += step);
ctx.fillText("Mean frame time : " + (totalFrameTime / totalFrameCount).toFixed(3)+"ms",10,y += step);
ctx.fillText("Frames dropped : " + Math.round(((time -startTime)- (totalFrameCount * frameRate)) / frameRate),10,y += step);
ctx.fillText("RenderLoad : " + lastFrameRenderTime.toFixed(3)+"ms Hold mouse into increase",10,y += step);
clearToY = y;
graph.step(atFrame);
requestAnimationFrame(loop);
if(mouse.button ){
renderLoadMs += 0.1;
var pt = performance.now();
while(performance.now() - pt < renderLoadMs);
}else{
renderLoadMs = 0;
}
lastFrameRenderTime = performance.now() - pTime;
}
requestAnimationFrame(loop);
canvas { border : 2px solid black; }
body { font-family : arial; font-size : 12px;}
<canvas id="canvas"></canvas>
<ul>
<li><span style="color:red">Red</span> is frame time error from the callback argument.</li>
<li><span style="color:yellow">Yellow</span> is the frame time error calculated using performance.now().</li>
<li><span style="color:cyan">Cyan</span> is the frame time error calculated using Date.now().</li>
<li><span style="color:#0F0">Green</span> dots are the difference in time between the callback time argument and the time reported by performance.now()</li>
<li><span style="color:magenta">Magenta</span> is the last frame's render time calculated using performance.now().</li>
<li><span style="color:green">Green</span> vertical lines indicate that a frame has been dropped / skipped</li>
<li>The dark blue and black background marks seconds.</li>
</ul>
For me I never use delta time for animation, and I accept that some frames will be lost. But overall you get a smoother animation using a fixed interval than attempting to correct the time post render.
The best way to get smooth animation is to reduce the render time to under 16ms, if you can't get that then use deltat time not to set animation frame but to selectively drop frames and maintain a rate of 30 frames per second.
The point of a delta time is to keep the frame-rate stable by compensating for time taken by computations.
Think of this code:
var framerate = 1000 / 60;
var exampleOne = function () {
/* computation that takes 10 ms */
setTimeout(exampleOne, framerate);
}
var exampleTwo = function () {
setTimeout(exampleTwo, framerate);
/* computation that takes 30 ms */
}
In example one the function would calculate for 10 ms and then wait the frame-rate before painting the next frame. This will inevitably lead to a frame-rate lower than the expected.
In example two the function would start the timer for the next iteration immediately and then calculate for 30 ms. This will lead to the next frame being painted before the previous is done calculating, bottle necking your application.
With delta-time you get the best of both worlds:
var framerate = 1000 / 60;
var exampleThree = function () {
var delta = Date.now();
/* computation that takes 10 to 30 ms */
var deltaTime = Date.now() - delta;
if (deltaTime >= framerate) {
requestAnimationFrame(exampleThree);
}
else {
setTimeout(function () { requestAnimationFrame(exampleThree); }, framerate - deltaTime);
}
};
With delta-time, which represents the calculation time, we know how much time we have left before the next frame needs to be painting.
We don't have the sliding performance from example one and we don't have a bunch of frames trying to draw at the same time as in example two.
How could I calculate the FPS of a canvas game application? I've seen some examples, but none of them use requestAnimationFrame, and im not sure how to apply their solutions there. This is my code:
(function(window, document, undefined){
var canvas = document.getElementById("mycanvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
fps = 0,
game_running = true,
show_fps = true;
function showFPS(){
context.fillStyle = "Black";
context.font = "normal 16pt Arial";
context.fillText(fps + " fps", 10, 26);
}
function gameLoop(){
//Clear screen
context.clearRect(0, 0, width, height);
if (show_fps) showFPS();
if (game_running) requestAnimationFrame(gameLoop);
}
gameLoop();
}(this, this.document))
canvas{
border: 3px solid #fd3300;
}
<canvas id="mycanvas" width="300" height="150"></canvas>
By the way, is there any library I could add to surpervise performance?
Do not use new Date()
This API has several flaws and is only useful for getting the current date + time. Not for measuring timespans.
The Date-API uses the operating system's internal clock, which is constantly updated and synchronized with NTP time servers. This means, that the speed / frequency of this clock is sometimes faster and sometimes slower than the actual time - and therefore not useable for measuring durations and framerates.
If someone changes the system time (either manually or due to DST), you could at least see the problem if a single frame suddenly needed an hour. Or a negative time. But if the system clock ticks 20% faster to synchronize with world-time, it is practically impossible to detect.
Also, the Date-API is very imprecise - often much less than 1ms. This makes it especially useless for framerate measurements, where one 60Hz frame needs ~17ms.
Instead, use performance.now()
The Performance API has been specificly made for such use cases and can be used equivalently to new Date(). Just take one of the other answers and replace new Date() with performance.now(), and you are ready to go.
Sources:
Also unlike Date.now(), the values returned by Performance.now()
always increase at a constant rate, independent of the system clock
(which might be adjusted manually or skewed by software like NTP).
Otherwise, performance.timing.navigationStart + performance.now() will
be approximately equal to Date.now().
https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
And for windows:
[The time service] adjusts the local clock rate to allow it to
converge toward the correct time.
If the time difference between the local clock and the [accurate time sample] is too large to correct by adjusting the local
clock rate,
the time service sets the local clock to the correct time.
https://technet.microsoft.com/en-us/library/cc773013(v=ws.10).aspx
Chrome has a built-in fps counter: https://developer.chrome.com/devtools/docs/rendering-settings
Just open the dev-console (F12), open the drawer (Esc), and add the "Rendering" tab.
Here, you can activate the FPS-Meter overlay to see the current framerate (incl. a nice graph), as well as GPU memory consumption.
Cross-browser solution:
You can get a similar overlay with the JavaScript library stat.js: https://github.com/mrdoob/stats.js/
It also provides a nice overlay for the framerate (incl. graph) and is very easy to use.
When comparing the results from stats.js and the chrome dev tools, both show the exact same measurements. So you can trust that library to actually do the correct thing.
You could keep track of the last time requestAnimFrame was called.
var lastCalledTime;
var fps;
function requestAnimFrame() {
if(!lastCalledTime) {
lastCalledTime = Date.now();
fps = 0;
return;
}
delta = (Date.now() - lastCalledTime)/1000;
lastCalledTime = Date.now();
fps = 1/delta;
}
http://jsfiddle.net/vZP3u/
Here's another solution:
var times = [];
var fps;
function refreshLoop() {
window.requestAnimationFrame(function() {
const now = performance.now();
while (times.length > 0 && times[0] <= now - 1000) {
times.shift();
}
times.push(now);
fps = times.length;
refreshLoop();
});
}
refreshLoop();
This improves on some of the others in the following ways:
performance.now() is used over Date.now() for increased precision (as covered in this answer)
FPS is measured over the last second so the number won't jump around so erratically, particularly for applications that have single long frames.
I wrote about this solution in more detail on my website.
I have a different approach, because if you calculate the the FPS you'll get this flickering when returning the number. I decided to count every Frame and return it once a second
window.countFPS = (function () {
var lastLoop = (new Date()).getMilliseconds();
var count = 1;
var fps = 0;
return function () {
var currentLoop = (new Date()).getMilliseconds();
if (lastLoop > currentLoop) {
fps = count;
count = 1;
} else {
count += 1;
}
lastLoop = currentLoop;
return fps;
};
}());
requestAnimationFrame(function () {
console.log(countFPS());
});
jsfiddle
I was missing an implementation that allows to customize the size of the sample for the averaged FPS value. Here is mine , it has the following features :
Accurate : performance.now() based
Stabilized : Returned FPS value is an averaged value ( fps.value | fps.tick() )
Configurable : FPS samples array size can be customized ( fps.samplesSize )
Efficient : Rotatory array for collecting samples (avoids array resizing)
const fps = {
sampleSize : 60,
value : 0,
_sample_ : [],
_index_ : 0,
_lastTick_: false,
tick : function(){
// if is first tick, just set tick timestamp and return
if( !this._lastTick_ ){
this._lastTick_ = performance.now();
return 0;
}
// calculate necessary values to obtain current tick FPS
let now = performance.now();
let delta = (now - this._lastTick_)/1000;
let fps = 1/delta;
// add to fps samples, current tick fps value
this._sample_[ this._index_ ] = Math.round(fps);
// iterate samples to obtain the average
let average = 0;
for(i=0; i<this._sample_.length; i++) average += this._sample_[ i ];
average = Math.round( average / this._sample_.length);
// set new FPS
this.value = average;
// store current timestamp
this._lastTick_ = now;
// increase sample index counter, and reset it
// to 0 if exceded maximum sampleSize limit
this._index_++;
if( this._index_ === this.sampleSize) this._index_ = 0;
return this.value;
}
}
// *******************
// test time...
// *******************
function loop(){
let fpsValue = fps.tick();
window.fps.innerHTML = fpsValue;
requestAnimationFrame( loop );
}
// set FPS calulation based in the last 120 loop cicles
fps.sampleSize = 120;
// start loop
loop()
<div id="fps">--</div>
Actually none of the answers were sufficient for me. Here is a better solution which:
Use's performance.now()
Calculates the actual average fps per second
Average per second and decimal places are configurable
Code:
// Options
const outputEl = document.getElementById('fps-output');
const decimalPlaces = 2;
const updateEachSecond = 1;
// Cache values
const decimalPlacesRatio = Math.pow(10, decimalPlaces);
let timeMeasurements = [];
// Final output
let fps = 0;
const tick = function() {
timeMeasurements.push(performance.now());
const msPassed = timeMeasurements[timeMeasurements.length - 1] - timeMeasurements[0];
if (msPassed >= updateEachSecond * 1000) {
fps = Math.round(timeMeasurements.length / msPassed * 1000 * decimalPlacesRatio) / decimalPlacesRatio;
timeMeasurements = [];
}
outputEl.innerText = fps;
requestAnimationFrame(() => {
tick();
});
}
tick();
JSFiddle
Just check the difference in time between the AFR-callbacks. AFR already passes the time as an argument to the callback. I updated your fiddle to show it: http://jsfiddle.net/WCKhH/1/
Just a proof of concept. Very simple code. All we do is set our frames per second and intervals between each frame. In the drawing function we deduct our last frame’s execution time from the current time to check whether the time elapsed since the last frame is more than our interval (which is based on the fps) or not. If the condition evaluates to true, we set the time for our current frame which is going to be the “last frame execution time” in the next drawing call.
var GameLoop = function(fn, fps){
var now;
var delta;
var interval;
var then = new Date().getTime();
var frames;
var oldtime = 0;
return (function loop(time){
requestAnimationFrame(loop);
interval = 1000 / (this.fps || fps || 60);
now = new Date().getTime();
delta = now - then;
if (delta > interval) {
// update time stuffs
then = now - (delta % interval);
// calculate the frames per second
frames = 1000 / (time - oldtime)
oldtime = time;
// call the fn
// and pass current fps to it
fn(frames);
}
}(0));
};
Usage:
var set;
document.onclick = function(){
set = true;
};
GameLoop(function(fps){
if(set) this.fps = 30;
console.log(fps);
}, 5);
http://jsfiddle.net/ARTsinn/rPAeN/
My fps calculation uses requestAnimationFrame() and the matching timestamp argument for its callback function.
See https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame and https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp.
No need for new Date() or performance.now()!
The rest is inspired heavily by other answers in this thread, especially https://stackoverflow.com/a/48036361/4706651.
var fps = 1;
var times = [];
var fpsLoop = function (timestamp) {
while (times.length > 0 && times[0] <= timestamp - 1000) {
times.shift();
}
times.push(timestamp);
fps = times.length;
console.log(fps);
requestAnimationFrame(fpsLoop);
}
requestAnimationFrame(fpsLoop);
The best way that I use with performance.now()
Simple I passed TIME on gameLoop function and
calculate fps
fps = 1 / ( (performance.now() - LAST_FRAME_TIME) / 1000 );
(function(window, document, undefined){
var canvas = document.getElementById("mycanvas"),
context = canvas.getContext("2d"),
width = canvas.width,
height = canvas.height,
fps = 0,
game_running = true,
show_fps = true,
LAST_FRAME_TIME = 0;
function showFPS(){
context.fillStyle = "Black";
context.font = "normal 16pt Arial";
context.fillText(fps + " fps", 10, 26);
}
function gameLoop(TIME){
//Clear screen
context.clearRect(0, 0, width, height);
if (show_fps) showFPS();
fps = 1 / ((performance.now() - LAST_FRAME_TIME) / 1000);
LAST_FRAME_TIME = TIME /* remember the time of the rendered frame */
if (game_running) requestAnimationFrame(gameLoop);
}
gameLoop();
}(this, this.document))
canvas{
border: 3px solid #fd3300;
}
<canvas id="mycanvas" width="300" height="150"></canvas>
i had to create a function which sets on which fps should animation run, because i have a 240hz monitor and animations on my screen are much faster then on other screens, so that my end projects was always slower on other monitors
function setFPSandRunAnimation(fps, cb) {
let frameCount = 0;
let fpsInterval, startTime, now, then, elapsed;
runAnimating(fps);
function runAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
animate();
}
function animate(timestamp) {
requestAnimationFrame(animate);
now = Date.now();
elapsed = now - then;
if (elapsed > fpsInterval) {
then = now - (elapsed % fpsInterval);
const sinceStart = now - startTime;
const currentFps = Math.round(1000 / (sinceStart / ++frameCount) * 100) / 100;
const elapsedTime = Math.round(sinceStart / 1000 * 100) / 100;
cb(timestamp, currentFps, elapsedTime)
}
}
}
this is how to you use it
setFPSandRunAnimation(fpsSpeedYouWant, cbFunctionWhereYouGet timestamp, currentfps and elapsedTime).
inside of the cb function you can run any code you would run in animation function