HTML Canvas: Animation Delay - javascript

Here is a demo of my Canvas.
The canvas generates a random rectangle and animates it by scaling it from 1.0 to 1.2 and back to 1.0 again. (Kinda like a human heart). This animation takes approximately 2 seconds to complete. There are 60 totalIterations. It starts with 0 and increments by one for every frame until it reaches 60. Once it reaches 60, the iteration is set back to 0 and animates from 1.2 scale back to 1.0.
What I want to do is before the execution of the next cycle (cycle meaning from 1.0 scale, to 1.2, and back to 1.0), I want to defer the scale.
Here is what I tried to do:
Context:
this.intermission = 3; // time to wait, or "defer" for
elapsed = (Date.now() - this.initTime) / 1000; // time elapsed since initialization (in seconds)
Condition:
if((elapsed % this.intermission >= (this.intermission - (this.intermission-1))) && (elapsed % this.intermission <= (this.intermission + (this.intermission-1)))) {
ctx.scale(this.easing, this.easing);
}
Condition Explained (Probably makes no sense):
If the remainder from dividing the elapsed time by 3 is greater than or equal to 2 AND the remainder from dividing the elapsed time by 3 is less than or equal to 5, scale the rectangle using the ease function.
... I wanted to give it some "buffer" room to complete the animation
If I were to increase the intermission to 10, the above condition would not work anymore, so I need a much better solution.
I thought about using setTimeout(function(){...}, x), but this is inside the JavaScript class.

Animation Lists / Stacks.
Keyframing
The best way would be to set up code to manage keyframes so you could just create a list of keyframes for each property of an object you wish to change over time. This allows you to create very complex animations that can be serialized to a JSON file. This decouples the animation from the code, but requires a lot more code.
Animation List
If it is just a simple animation then you can create an animation stack (or list if animation order is static), which is just a set of functions that get called in turn for each part of the animation.
You set a startTime for each part of the animation, being careful to set it consistently so you do not get any time drift. If the animation cycle is 4 seconds long then it should repeat every 4 seconds and in 4,000,000 seconds it should be just as precise.
Always use requestAnimationFrame (rAF) when animating anything on the page. rAF calls the callback passing as the first argument the time in ms (1/1000th) with a precision of 1/1,000,000 (0.001ms).
Endless animation using animation list
const canvas = document.createElement("canvas");
canvas.height = canvas.width = 300;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas)
// ease function
function easeInOut(x, pow = 2) {
x = x < 0 ? 0: x > 1 ? 1 : x;
var xx = Math.pow(x,pow);
return xx/(xx+Math.pow(1-x,pow));
};
function MyObj(){
this.x = 100;
this.y = 100;
this.size = 40;
this.scale = 1;
}
MyObj.prototype = {
getUnitTime(duration){ // get the unit time
var unitTime = (globalTime - startTime) / duration;
if(unitTime >= 1){ // if over time
unitTime = 1; // make sure that the current frame is not over
startTime = startTime + duration; // next frame start (could be in the past)
currentAnim += 1; // next animation in the list
}
return unitTime;
},
grow(){
drawText("Grow 1s");
// grow for 1 second
this.scale = easeInOut(this.getUnitTime(1000)) * 0.6 + 1;
},
shrink(){
drawText("Shrink 1s");
// shrink for 1 second
this.scale = 1.6 - easeInOut(this.getUnitTime(1000)) * 0.6 ;
},
wait(){
drawText("Wait 2s");
this.getUnitTime(2000); // wait two seconds
},
draw(ctx){
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * this.scale, 0, Math.PI * 2);
ctx.fill();
}
}
function drawText(text){
ctx.fillStyle = "black";
ctx.fillText(text,100,36);
}
var obj = new MyObj(); // create the object
// holds the animation list
const animationList = [
obj.grow.bind(obj), // bind the function calls to the object
obj.shrink.bind(obj),
obj.wait.bind(obj)
];
var currentAnim; // index of current animation
var startTime; // start time of current animation
var globalTime; // time from the requestAnimationFrame callback argument
ctx.font = "32px arial";
ctx.textAlign = "center";
// main animation loop
function update(time){
globalTime = time; // set the global
if(currentAnim === undefined){ // if not set then
startTime = time; // set start time
currentAnim = 0; // set the index of the first animation
}
// clear the screen
ctx.clearRect(0,0,canvas.width,canvas.height);
// call the animation function
animationList[currentAnim % animationList.length]();
// draw the object
obj.draw(ctx);
// request next frame
requestAnimationFrame(update);
}
// start it all happening
requestAnimationFrame(update);
Stacks
Stacks are much the same but used when the animation is conditional. You use some event to push the animation functions onto the stack. Then you shift the animation functions from the stack as needed. Or you may want the animation to repeat 10 times, then do something else, then start again. The animation stack lets you do this rather than have a huge list of animations.
Stack example using click event.
const canvas = document.createElement("canvas");
canvas.height = canvas.width = 300;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas)
// ease function
function easeInOut(x, pow = 2) {
x = x < 0 ? 0: x > 1 ? 1 : x;
var xx = Math.pow(x,pow);
return xx/(xx+Math.pow(1-x,pow));
};
function MyObj(){
this.x = 100;
this.y = 100;
this.size = 40;
this.scale = 1;
}
MyObj.prototype = {
getUnitTime(duration){ // get the unit time
var unitTime = (globalTime - startTime) / duration;
if(unitTime >= 1){ // if over time
unitTime = 1; // make sure that the current frame is not over
startTime = startTime + duration; // next frame start (could be in the past)
currentAnim = undefined
}
return unitTime;
},
grow(){
drawText("Grow 1s");
// grow for 1 second
this.scale = easeInOut(this.getUnitTime(1000)) * 0.6 + 1;
},
shrink(){
drawText("Shrink 1s");
// shrink for 1 second
this.scale = 1.6 - easeInOut(this.getUnitTime(1000)) * 0.6 ;
},
timeup(){
drawText("Click to Animate");
currentAnim = undefined;
},
draw(ctx){
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * this.scale, 0, Math.PI * 2);
ctx.fill();
}
}
function drawText(text){
ctx.fillStyle = "black";
ctx.fillText(text,100,36);
}
var obj = new MyObj(); // create the object
// holds the animation list
const animationStack = [obj.timeup.bind(obj)];
var currentAnim; // index of current animation
var startTime; // start time of current animation
var globalTime; // time from the requestAnimationFrame callback argument
ctx.font = "26px arial";
ctx.textAlign = "center";
function startAnim(){
animationStack.length = 0;
animationStack.push(obj.grow.bind(obj));
animationStack.push(obj.shrink.bind(obj));
animationStack.push(obj.timeup.bind(obj));
if(currentAnim === undefined){// only restart if animation is not running
requestAnimationFrame(update);
}
startTime = undefined;
currentAnim = undefined;
}
canvas.addEventListener("click",startAnim)
// main animation loop
function update(time){
globalTime = time; // set the global
if(startTime === undefined){ // if not set then
startTime = time; // set start time
}
if(currentAnim === undefined){
if(animationStack.length > 0){
currentAnim = animationStack.shift();
}
}
if(currentAnim === undefined){
return;
}
// clear the screen
ctx.clearRect(0,0,canvas.width,canvas.height);
// call the animation function
currentAnim();
// draw the object
obj.draw(ctx);
// request next frame
requestAnimationFrame(update);
}
// start it all happening
requestAnimationFrame(update);

Related

How to make an object move in p5.js in a given time?

Suppose I have a canvas that is 1200px. How do I get an object to move from the starting point (100px) to its endpoint (1000px) within a given time (eg. 10 seconds)? In such a way that it takes the object exactly 10 seconds to traverse from starting point to endpoint.
My code looks like this so far:
function setup()
{
createCanvas(img.width, img.height);
//Initialize x with the start value
x = startX;
}
function draw()
{
image(img, 0, 0);
x = min(endX, x);
x+=2;
//stop the object if it's near enough to endx and endy
if (abs(endX - x) < 30)
{
x = endX;
}
y = 114;
//stop the object if it goes off of the screen
x = min(x, 1200);
x = max(x, 0);
var spotlightSize = 114;
blendMode(BLEND);
background(10);
image(spotlight_image, x-spotlightSize/2, y-spotlightSize/2, spotlightSize, spotlightSize);
blendMode(DARKEST);
image(img, 0, 0);
}
If the frame rate was perfect and constant, you could simply divide the distance to travel by the amount of frames in the time that it takes. That result would be how far you need to travel in each frame. The frame rate is not perfect or constant, but we will write a program assuming a perfect frame rate because it will help us later.
What you need to do is:
Find how many frames will pass in the time you want to move - multiply the time to move by the frames per second
Find the displacement from the start to the end - subtract the start from the end
Divide the displacement by the amount of frames that will pass
Move that far each frame until you are close enough to the end
An example implementation: (you used only x-position but I used vectors as they will probably be useful to someone in the future)
new p5();
const fps = 60; // frames per second
const startPos = createVector(100, 50);
var position = startPos.copy();
const endPos = createVector(600, 450);
const stopAtDist = 30; // stop if it's this far from the end point in any direction
const distToTravel = p5.Vector.sub(endPos, startPos);
const moveDurationS = 10; // Move duration in seconds
const moveDurationFrames = moveDurationS / (1 / fps); // How many frames will it take to move the distance
const distToMovePerFrame = p5.Vector.div(distToTravel, moveDurationFrames); // How far to move each frame
var currentlyMoving = true;
function setup() {
createCanvas(800, 500);
frameRate(fps);
}
function draw() {
background(0);
// Draw the start pos
stroke('yellow');
strokeWeight(10);
point(startPos.x, startPos.y);
// Draw the end pos
stroke('green');
point(endPos.x, endPos.y);
// Draw the current position
stroke('red');
point(position.x, position.y);
// If it's currently moving, then move
if (currentlyMoving) {
position.add(distToMovePerFrame);
}
// If it is close enough to the end, then stop
if (abs(dist(position.x, position.y, endPos.x, endPos.y)) < stopAtDist) {
currentlyMoving = false;
}
}
The frame rate is not constant, though. Fortunately, p5 has a function that tells us how many milliseconds have passed in the last frame. So what we do is:
Find how many milliseconds pass in the time you want to move - multiply the seconds you want it to move for by 1000
Find out how far it will move per millisecond - divide the start/end displacement by the amount of milliseconds that will pass
Each frame, move the distance per millisecond multiplied by how many milliseconds have gone past in that frame.
Here's that translated into code:
new p5();
const fps = 60; // frames per second
const startPos = createVector(100, 50);
var position = startPos.copy();
const endPos = createVector(600, 450);
const stopAtDist = 30; // stop if it's this far from the end point in any direction
const distToTravel = p5.Vector.sub(endPos, startPos);
const moveDurationS = 10;
const moveDurationMs = moveDurationS * 1000;
const distToMovePerMs = p5.Vector.div(distToTravel, moveDurationMs);
var currentlyMoving = true;
function setup() {
createCanvas(800, 500);
frameRate(fps);
}
function draw() {
background(0);
// Draw the start pos
stroke('yellow');
strokeWeight(10);
point(startPos.x, startPos.y);
// Draw the end pos
stroke('green');
point(endPos.x, endPos.y);
// Draw the current position
stroke('red');
point(position.x, position.y);
// If it's currently moving, then move
if (currentlyMoving) {
var thisFrameMovement = p5.Vector.mult(distToMovePerMs, deltaTime);
position.add(thisFrameMovement);
}
// If it is close enough to the end, then stop
if (abs(dist(position.x, position.y, endPos.x, endPos.y)) < stopAtDist) {
currentlyMoving = false;
}
}
I tested the above code and it was pretty accurate - it averaged 0.75% off. I hope that this is what you're looking for in your answer!

requestAnimationFrame JavaScript: Constant Frame Rate / Smooth Graphics

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.

simple animation with javascript interval

I'm set up a simple animation with set Interval.
But I wanted to do an animation where it moves to a target position over time.
I used to this back in the flash days and have forgotten the process.
It remember using something like
property = (target - property)/speed;
But having a problem setting that up with the below setup.
I understand there is a 100 ways to do this and even using css but I just want to know how I could implement with that I have now. I just want that ease over time to happen with setInterval.
var sq = document.querySelector('.square');
button = document.querySelector('button');
var interval, toggle = 0, pos=0;
var targetX = 100;
var startX = 0;
button.addEventListener("click", (event) =>{
toggle += 1;
toggle = toggle % 2;
if( toggle > 0){
interval = setInterval(animate, 5);
}else{
clearInterval(interval);
}
});
function animate(){
//pos++
//sq.style.left = pos + 'px';
sq.style.left = (targetX - sq.style.left) / speed
}
Using setInterval() isn't the best choice for animations since it cannot sync to monitor updates. A better choice, and highly recommended, is requestAnimationFrame().
Right now the code uses an interval of 5ms which is way too low. The shortest frame update happens at 16.7ms (at 60Hz screens) so there is much overhead here.
I would also suggest using linear interpolation (AKA lerp) to do the animation, and time duration to define speed. Using interpolation allows you to further feed the t into an easing function.
Example
t is calculated based on start-time, current time and duration
t, now normalized to [0, 1] is fed to lerp function to obtain new position
translateX is used to move div to allow subpixeled animation. Final position can still be set by removing translation and a fixed position for left.
var reqId, // request ID so we can cancel anim.
startTime = 0,
source = 0,
target = 300,
duration = 1000, // duration in ms
sq = document.querySelector("div");
document.querySelector("button").onclick = function() {
startTime = 0; // reset time for t
cancelAnimationFrame(reqId); // abort existing animation if any
reqId = requestAnimationFrame(loop); // start new with time argument
function loop(time) {
if (!startTime) startTime = time; // set start time if not set already
t = (time - startTime) / duration; // calc t
var x = lerp(source, target, t); // lerp between source and target position
sq.style.transform = "translateX("+x+"px)"; // use transform to allow sub-pixeling
if (t < 1) requestAnimationFrame(loop); // loop until t=1
// else { here you can remove transform and set final position ie. using left}
}
// basic linear interpolation
function lerp(v1, v2, t) {return v1 + (v2-v1) * t}
};
div {position:absolute; left:0; top:40px; width:100px;height:100px;background:#c33}
<button>Animate</button>
<div></div>

Grass like smoothing animation on beziercurve?

This is what I am trying to achieve--GRASS Animation(Desired animation)
This is where the project is standing currently --My hair animation
This is a more structurised code of the above code --My hair animation(by markE)--markE`s code of hair animation
PROBLEM:--
I am able to give movements to hairs but animation should be more like wavy grass like freeflowing.Its not very smooth now.What can be done to make the hairs flow in more natural manner.
Please provide me with a small sample if possible!!!
<canvas id="myCanvas" width="500" height="500" style="background-color: antiquewhite" ></canvas>
JAVASCRIPT
//mouse position
var x2=0;
var y2=0;
window.addEventListener("mousemove",function(){moving(event);init()},false)
//these variables define the bend in our bezier curve
var bend9=0;
var bend8=0;
var bend7=0;
var bend6=0;
var bend5=0;
var bend4=0;
var bend3=0;
var bend2=0;
var bend1=0;
//function to get the mouse cordinates
function moving(event) {
bend_value();//this function is defined below
try
{
x2 = event.touches[0].pageX;
y2 = event.touches[0].pageY;
}
catch (error)
{
try
{
x2 = event.clientX;
y2 = event.clientY;
}
catch (e)
{
}
}
try
{
event.preventDefault();
}
catch (e)
{
}
if(between(y2,204,237) && between(x2,115,272))
{
console.log("Xmove="+x2,"Ymove="+y2)
}
}
//function for declaring range of bezier curve
function between(val, min, max)
{
return val >= min && val <= max;
}
(function() {
hair = function() {
return this;
};
hair.prototype={
draw_hair:function(a,b,c,d,e,f,g,h){
var sx =136+a;//start position of curve.used in moveTo(sx,sy)
var sy =235+b;
var cp1x=136+c;//control point 1
var cp1y=222+d;
var cp2x=136+e;//control point 2
var cp2y=222+f;
var endx=136+g;//end points
var endy=210+h;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
// context.clearRect(0, 0,500,500);
context.strokeStyle="grey";
context.lineWidth="8";
context.beginPath();
context.moveTo(sx,sy);
context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,endx,endy);
context.lineCap = 'round';
context.stroke();
// context.restore();
// context.save();
}
};
})();
//this function provides and calculate the bend on mousemove
function bend_value(){
var ref1=135;//this is ref point for hair or curve no 1
var ref2=150;//hair no 2 and so on
var ref3=165;
var ref4=180;
var ref5=195;
var ref6=210;
var ref7=225;
var ref8=240;
var ref9=255;
if(between(x2,115,270) && between(y2,205,236))
{
if(x2>=135 && x2<=145){bend1=(x2-ref1)*(2.2);}
if(x2<=135 && x2>=125){bend1=(x2-ref1)*(2.2);}
if(x2>=150 && x2<=160){bend2=(x2-ref2)*(2.2);}
if(x2<=150 && x2>=140){bend2=(x2-ref2)*(2.2);}
if(x2>=165 && x2<=175){bend3=(x2-ref3)*(2.2);}
if(x2<=165 && x2>=155){bend3=(x2-ref3)*(2.2);}
if(x2>=180 && x2<=190){bend4=(x2-ref4)*(2.2);}
if(x2<=180 && x2>=170){bend4=(x2-ref4)*(2.2);}
if(x2>=195 && x2<=205){bend5=(x2-ref5)*(2.2);}
if(x2<=195 && x2>=185){bend5=(x2-ref5)*(2.2);}
if(x2>=210 && x2<=220){bend6=(x2-ref6)*(2.2);}
if(x2<=210 && x2>=200){bend6=(x2-ref6)*(2.2);}
if(x2>=225 && x2<=235){bend7=(x2-ref7)*(2.2);}
if(x2<=225 && x2>=215){bend7=(x2-ref7)*(2.2);}
if(x2>=240 && x2<=250){bend8=(x2-ref8)*(2.2);}
if(x2<=240 && x2>=230){bend8=(x2-ref8)*(2.2);}
if(x2>=255 && x2<=265){bend9=(x2-ref9)*(2.2);}
if(x2<=255 && x2>=245){bend9=(x2-ref9)*(2.2);}
}
}
function init(){//this function draws each hair/curve
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var clear=context.clearRect(0, 0,500,500);
var save=context.save();
// /* console.log("bend2="+bend2)
// console.log("bend3="+bend3)
// console.log("bend4="+bend4)
// console.log("bend5="+bend5)
// console.log("bend6="+bend6)
// console.log("bend7="+bend7)
// console.log("bend8="+bend8)
// console.log("bend9="+bend9)*/
hd1 = new hair();//hd1 stands for hair draw 1.this is an instance created for drawing hair no 1
clear;
hd1.draw_hair(0,0,0,0,0,0,0+bend1/2,0);//these parameters passed to function drawhair and bend is beint retrieved from function bend_value()
save;
hd2 = new hair();
clear;
hd2.draw_hair(15,0,15,0,15,0,15+bend2/2,0);
save;
hd3 = new hair();
clear;
hd3.draw_hair(30,0,30,0,30,0,30+bend3/2,0);
save;
hd4 = new hair();
clear;
hd4.draw_hair(45,0,45,0,45,0,45+bend4/2,0);
save;
hd5 = new hair();
clear;
hd5.draw_hair(60,0,60,0,60,0,60+bend5/2,0);
save;
}
window.onload = function() {
init();
disableSelection(document.body)
}
function disableSelection(target){
if (typeof target.onselectstart!="undefined") //IE
target.onselectstart=function(){return false}
else if (typeof target.style.MozUserSelect!="undefined") //Firefox
target.style.MozUserSelect="none"
else //All other ie: Opera
target.onmousedown=function(){return false}
target.style.cursor = "default"
}
Update: I'm currently adjusting the code to produce the requested result and commenting it.
(function() { // The code is encapsulated in a self invoking function to isolate the scope
"use strict";
// The following lines creates shortcuts to the constructors of the Box2D types used
var B2Vec2 = Box2D.Common.Math.b2Vec2,
B2BodyDef = Box2D.Dynamics.b2BodyDef,
B2Body = Box2D.Dynamics.b2Body,
B2FixtureDef = Box2D.Dynamics.b2FixtureDef,
B2Fixture = Box2D.Dynamics.b2Fixture,
B2World = Box2D.Dynamics.b2World,
B2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
B2RevoluteJoint = Box2D.Dynamics.Joints.b2RevoluteJoint,
B2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;
// This makes sure that there is a method to request a callback to update the graphics for next frame
window.requestAnimationFrame =
window.requestAnimationFrame || // According to the standard
window.mozRequestAnimationFrame || // For mozilla
window.webkitRequestAnimationFrame || // For webkit
window.msRequestAnimationFrame || // For ie
function (f) { window.setTimeout(function () { f(Date.now()); }, 1000/60); }; // If everthing else fails
var world = new B2World(new B2Vec2(0, -10), true), // Create a world with gravity
physicalObjects = [], // Maintain a list of the simulated objects
windInput = 0, // The input for the wind in the current frame
wind = 0, // The current wind (smoothing the input values + randomness)
STRAW_COUNT = 10, // Number of straws
GRASS_RESET_SPEED = 2, // How quick should the straw reset to its target angle
POWER_MOUSE_WIND = 120, // How much does the mouse affect the wind
POWER_RANDOM_WIND = 180; // How much does the randomness affect the wind
// GrassPart is a prototype for a piece of a straw. It has the following properties
// position: the position of the piece
// density: the density of the piece
// target: the target angle of the piece
// statik: a boolean stating if the piece is static (i.e. does not move)
function GrassPart(position, density, target, statik) {
this.width = 0.05;
this.height = 0.5;
this.target = target;
// To create a physical body in Box2D you have to setup a body definition
// and create at least one fixture.
var bdef = new B2BodyDef(), fdef = new B2FixtureDef();
// In this example we specify if the body is static or not (the grass roots
// has to be static to keep the straw in its position), and its original
// position.
bdef.type = statik? B2Body.b2_staticBody : B2Body.b2_dynamicBody;
bdef.position.SetV(position);
// The fixture of the piece is a box with a given density. The negative group index
// makes sure that the straws does not collide.
fdef.shape = new B2PolygonShape();
fdef.shape.SetAsBox(this.width/2, this.height/2);
fdef.density = density;
fdef.filter.groupIndex = -1;
// The body and fixture is created and added to the world
this.body = world.CreateBody(bdef);
this.body.CreateFixture(fdef);
}
// This method is called for every frame of animation. It strives to reset the original
// angle of the straw (the joint). The time parameter is unused here but contains the
// current time.
GrassPart.prototype.update = function (time) {
if (this.joint) {
this.joint.SetMotorSpeed(GRASS_RESET_SPEED*(this.target - this.joint.GetJointAngle()));
}
};
// The link method is used to link the pieces of the straw together using a joint
// other: the piece to link to
// torque: the strength of the joint (stiffness)
GrassPart.prototype.link = function(other, torque) {
// This is all Box2D specific. Look it up in the manual.
var jdef = new B2RevoluteJointDef();
var p = this.body.GetWorldPoint(new B2Vec2(0, 0.5)); // Get the world coordinates of where the joint
jdef.Initialize(this.body, other.body, p);
jdef.maxMotorTorque = torque;
jdef.motorSpeed = 0;
jdef.enableMotor = true;
// Add the joint to the world
this.joint = world.CreateJoint(jdef);
};
// A prototype for a straw of grass
// position: the position of the bottom of the root of the straw
function Grass(position) {
var pos = new B2Vec2(position.x, position.y);
var angle = 1.2*Math.random() - 0.6; // Randomize the target angle
// Create three pieces, the static root and to more, and place them in line.
// The second parameter is the stiffness of the joints. It controls how the straw bends.
// The third is the target angle and different angles are specified for the pieces.
this.g1 = new GrassPart(pos, 1, angle/4, true); // This is the static root
pos.Add(new B2Vec2(0, 1));
this.g2 = new GrassPart(pos, 0.75, angle);
pos.Add(new B2Vec2(0, 1));
this.g3 = new GrassPart(pos, 0.5);
// Link the pieces into a straw
this.g1.link(this.g2, 20);
this.g2.link(this.g3, 3);
// Add the pieces to the list of simulate objects
physicalObjects.push(this.g1);
physicalObjects.push(this.g2);
physicalObjects.push(this.g3);
}
Grass.prototype.draw = function (context) {
var p = new B2Vec2(0, 0.5);
var p1 = this.g1.body.GetWorldPoint(p);
var p2 = this.g2.body.GetWorldPoint(p);
var p3 = this.g3.body.GetWorldPoint(p);
context.strokeStyle = 'grey';
context.lineWidth = 0.4;
context.lineCap = 'round';
context.beginPath();
context.moveTo(p1.x, p1.y);
context.quadraticCurveTo(p2.x, p2.y, p3.x, p3.y);
context.stroke();
};
var lastX, grass = [], context = document.getElementById('canvas').getContext('2d');
function updateGraphics(time) {
window.requestAnimationFrame(updateGraphics);
wind = 0.95*wind + 0.05*(POWER_MOUSE_WIND*windInput + POWER_RANDOM_WIND*Math.random() - POWER_RANDOM_WIND/2);
windInput = 0;
world.SetGravity(new B2Vec2(wind, -10));
physicalObjects.forEach(function(obj) { if (obj.update) obj.update(time); });
world.Step(1/60, 8, 3);
world.ClearForces();
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.save();
context.translate(context.canvas.width/2, context.canvas.height/2);
context.scale(context.canvas.width/20, -context.canvas.width/20);
grass.forEach(function (o) { o.draw(context); });
context.restore();
}
document.getElementsByTagName('body')[0].addEventListener("mousemove", function (e) {
windInput = Math.abs(lastX - e.x) < 200? 0.2*(e.x - lastX) : 0;
lastX = e.x;
});
var W = 8;
for (var i = 0; i < STRAW_COUNT; i++) {
grass.push(new Grass(new B2Vec2(W*(i/(STRAW_COUNT-1))-W/2, -1)));
}
window.requestAnimationFrame(updateGraphics);
})();
Waving grass algorithm
UPDATE
I made a reduced update to better meet what I believe is your requirements. To use mouse you just calculate the angle between the mouse point and the strain root and use that for new angle in the update.
I have incorporated a simple mouse-move sensitive approach which makes the strains "point" towards the mouse, but you can add random angles to this as deltas and so forth. Everything you need is as said in the code - adjust as needed.
New fiddle (based on previous with a few modifications):
http://jsfiddle.net/AbdiasSoftware/yEwGc/
Image showing 150 strains being simulated.
Grass simulation demo:
http://jsfiddle.net/AbdiasSoftware/5z89V/
This will generate a nice realistic looking grass field. The demo has 70 grass rendered (works best in Chrome or just lower the number for Firefox).
The code is rather simple. It consists of a main object (grassObj) which contains its geometry as well as functions to calculate the angles, segments, movements and so forth. I'll show this in detail below.
First some inits that are accessed globally by the functions:
var numOfGrass = 70, /// number of grass strains
grass,
/// get canvas context
ctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
/// we use an animated image for the background
/// The image also clears the canvas for each loop call
/// I rendered the clouds in a 3D software.
img = document.createElement('img'),
ix = 0, /// background image position
iw = -1; /// used for with and initial for flag
/// load background image, use it whenever it's ready
img.onload = function() {iw = this.width}
img.src = 'http://i.imgur.com/zzjtzG7.jpg';
The heart - grassObj
The main object as mentioned above is the grassObj:
function grassObj(x, y, seg1, seg2, maxAngle) {
/// exposed properties we need for rendering
this.x = x; /// bottom position of grass
this.y = y;
this.seg1 = seg1; /// segments of grass
this.seg2 = seg2;
this.gradient = getGradient(Math.random() * 50 + 50, 100 * Math.random() + 170);
this.currentAngle; ///current angle that will be rendered
/// internals used for calculating new angle, goal, difference and speed
var counter, /// counter between 0-1 for ease-in/out
delta, /// random steps in the direction goal rel. c.angle.
angle, /// current angle, does not change until goal is reached
diff, /// diff between goal and angle
goal = getAngle();
/// internal: returns an angel between 0 and maxAngle
function getAngle() {
return maxAngle * Math.random();
}
/// ease in-out function
function easeInOut(t) {
return t < 0.5 ? 4 * t * t * t : (t-1) * (2 * t - 2) * (2 * t - 2) + 1;
}
/// sets a new goal for grass to move to. Does the main calculations
function newGoal() {
angle = goal; /// set goal as new angle when reached
this.currentAngle = angle;
goal = getAngle(); /// get new goal
diff = goal - angle; /// calc diff
counter = 0; /// reset counter
delta = (4 * Math.random() + 1) / 100;
}
/// creates a gradient for this grass to increase realism
function getGradient(min, max) {
var g = ctx.createLinearGradient(0, 0, 0, h);
g.addColorStop(1, 'rgb(0,' + parseInt(min) + ', 0)');
g.addColorStop(0, 'rgb(0,' + parseInt(max) + ', 0)');
return g;
}
/// this is called from animation loop. Counts and keeps tracks of
/// current position and calls new goal when current goal is reached
this.update = function() {
/// count from 0 to 1 with random delta value
counter += delta;
/// if counter passes 1 then goal is reached -> get new goal
if (counter > 1) {
newGoal();
return;
}
/// ease in/out function
var t = easeInOut(counter);
/// update current angle for render
this.currentAngle = angle + t * diff;
}
/// init
newGoal();
return this;
}
Grass generator
We call makeGrass to generate grass at random positions, random heights and with random segments. The function is called with number of grass to render, width and height of canvas to fill and a variation variable in percent (0 - 1 float).
The single grass consist only of four points in total. The two middle points are spread about 1/3 and 2/3 of the total height with a little variation to break pattern. The points when rendered, are smoother using a cardinal spline with full tension to make the grass look smooth.
function makeGrass(numOfGrass, width, height, hVariation) {
/// setup variables
var x, y, seg1, seg2, angle,
hf = height * hVariation, /// get variation
i = 0,
grass = []; /// array to hold the grass
/// generate grass
for(; i < numOfGrass; i++) {
x = width * Math.random(); /// random x position
y = height - hf * Math.random(); /// random height
/// break grass into 3 segments with random variation
seg1 = y / 3 + y * hVariation * Math.random() * 0.1;
seg2 = (y / 3 * 2) + y * hVariation * Math.random() * 0.1;
grass.push(new grassObj(x, y, seg1, seg2, 15 * Math.random() + 50));
}
return grass;
}
Render
The render function just loops through the objects and updates the current geometry:
function renderGrass(ctx, grass) {
/// local vars for animation
var len = grass.length,
i = 0,
gr, pos, diff, pts, x, y;
/// renders background when loaded
if (iw > -1) {
ctx.drawImage(img, ix--, 0);
if (ix < -w) {
ctx.drawImage(img, ix + iw, 0);
}
if (ix <= -iw) ix = 0;
} else {
ctx.clearRect(0, 0, w, h);
}
/// loops through the grass object and renders current state
for(; gr = grass[i]; i++) {
x = gr.x;
y = gr.y;
ctx.beginPath();
/// calculates the end-point based on length and angle
/// Angle is limited [0, 60] which we add 225 deg. to get
/// it upwards. Alter 225 to make grass lean more to a side.
pos = lineToAngle(ctx, x, h, y, gr.currentAngle + 225);
/// diff between end point and root point
diff = (pos[0] - x)
pts = [];
/// starts at bottom, goes to top middle and then back
/// down with a slight offset to make the grass
pts.push(x); /// first couple at bottom
pts.push(h);
/// first segment 1/4 of the difference
pts.push(x + (diff / 4));
pts.push(h - gr.seg1);
/// second segment 2/3 of the difference
pts.push(x + (diff / 3 * 2));
pts.push(h - gr.seg2);
pts.push(pos[0]); /// top point
pts.push(pos[1]);
/// re-use previous data, but go backward down to root again
/// with a slight offset
pts.push(x + (diff / 3 * 2) + 10);
pts.push(h - gr.seg2);
pts.push(x + (diff / 4) + 12);
pts.push(h - gr.seg1 + 10);
pts.push(x + 15); /// end couple at bottom
pts.push(h);
/// smooth points (extended context function, see demo)
ctx.curve(pts, 0.8, 5);
ctx.closePath();
/// fill grass with its gradient
ctx.fillStyle = gr.gradient;
ctx.fill();
}
}
Animate
The main loop where we animate everything:
function animate() {
/// update each grass objects
for(var i = 0;i < grass.length; i++) grass[i].update();
/// render them
renderGrass(ctx, grass);
/// loop
requestAnimationFrame(animate);
}
And that's all there is to it for this version.
Darn! Late to the party...
But LOTS of neat answers here -- I'm upvoting all !
Anyway, here's my idea:
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/MJjHz/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.1/jquery-ui.js"></script>
<style>
body { font-family: arial; padding:15px; }
canvas { border: 1px solid red;}
input[type="text"]{width:35px;}
</style>
</head>
<body>
<p>Move mouse across hairs</p>
<canvas height="100" width="250" id="canvas"></canvas>
<script>
$(function() {
var canvas=document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var cHeight=canvas.height;
var showControls=false;
var lastMouseX=0;
// preset styling CONSTANTS
var SWAY=.55; // max endpoint sway from center
var C1Y=.40; // fixed Y of cp#1
var C2SWAY=.20 // max cp#2 sway from center
var C2Y=.75; // fixed Y of cp#2
var YY=20; // max height of ellipse at top of hair
var PIPERCENT=Math.PI/100;
var hairs=[];
// create hairs
var newHairX=40;
var hairCount=20;
for(var i=0;i<hairCount;i++){
var randomLength=50+parseInt(Math.random()*5);
addHair(newHairX+(i*8),randomLength);
}
function addHair(x,length){
hairs.push({
x:x,
length:length,
left:0,
right:0,
top:0,
s:{x:0,y:0},
c1:{x:0,y:0},
c2:{x:0,y:0},
e:{x:0,y:0},
isInMotion:false,
currentX:0
});
}
for(var i=0;i<hairs.length;i++){
var h=hairs[i];
setHairPointsFixed(h);
setHairPointsPct(h,50);
draw(h);
}
function setHairPointsFixed(h){
h.s.x = h.x;
h.s.y = cHeight;
h.c1.x = h.x;
h.c1.y = cHeight-h.length*C1Y;
h.c2.y = cHeight-h.length*C2Y;
h.top = cHeight-h.length;
h.left = h.x-h.length*SWAY;
h.right = h.x+h.length*SWAY;
}
function setHairPointsPct(h,pct){
// endpoint
var a=Math.PI+PIPERCENT*pct;
h.e.x = h.x - ((h.length*SWAY)*Math.cos(a));
h.e.y = h.top + (YY*Math.sin(a));
// controlpoint#2
h.c2.x = h.x + h.length*(C2SWAY*2*pct/100-C2SWAY);
}
//////////////////////////////
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// draw this frame based on mouse moves
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<hairs.length;i++){
hairMoves(hairs[i],mouseX,mouseY);
}
lastMouseX=mouseX;
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
function hairMoves(h,mouseX,mouseY){
// No hair movement if not touching hair
if(mouseY<cHeight-h.length-YY){
if(h.isInMotion){
h.isInMotion=false;
setHairPointsPct(h,50);
}
draw(h);
return;
}
// No hair movement if too deep in hair
if(mouseY>h.c1.y){
draw(h);
return;
}
//
var pct=50;
if(mouseX>=h.left && mouseX<=h.right){
if(h.isInMotion){
var pct=-(mouseX-h.right)/(h.right-h.left)*100;
setHairPointsPct(h,pct);
draw(h);
}else{
// if hair is at rest
// but mouse has just contacted hair
// set hair in motion
if( (lastMouseX<=h.x && mouseX>=h.x )
||(lastMouseX>=h.x && mouseX<=h.x )
){
h.isInMotion=true;
var pct=-(mouseX-h.right)/(h.right-h.left)*100;
}
setHairPointsPct(h,pct);
draw(h);
}
}else{
if(h.isInMotion){
h.isInMotion=false;
setHairPointsPct(h,50);
};
draw(h);
}
}
function dot(pt,color){
ctx.beginPath();
ctx.arc(pt.x,pt.y,5,0,Math.PI*2,false);
ctx.closePath();
ctx.fillStyle=color;
ctx.fill();
}
function draw(h){
ctx.beginPath();
ctx.moveTo(h.s.x,h.s.y);
ctx.bezierCurveTo(h.c1.x,h.c1.y,h.c2.x,h.c2.y,h.e.x,h.e.y);
ctx.strokeStyle="orange";
ctx.lineWidth=3;
ctx.stroke();
if(showControls){
dot(h.s,"green");
dot(h.c1,"red");
dot(h.c2,"blue");
dot(h.e,"purple");
ctx.beginPath();
ctx.rect(h.left,h.top-YY,(h.right-h.left),h.length*(1-C1Y)+YY)
ctx.lineWidth=1;
ctx.strokeStyle="lightgray";
ctx.stroke();
}
}
});
</script>
</body>
</html>
Here is a simple hair simulation that seems to be what you are looking for. The basic idea is to draw a bezier curve (in this case I use two curves to provide thickness for the hair). The curve will have a base, a bending point, and a tip. I set the bending point halfway up the hair. The tip of the hair will rotate about the axis of the base of the hair in response to mouse movement.
Place this code in a script tag below the canvas element declaration.
function Point(x, y) {
this.x = x;
this.y = y;
}
function Hair( ) {
this.height = 100; // hair height
this.baseWidth = 3; // hair base width.
this.thickness = 1.5; // hair thickness
this.points = {};
this.points.base1 = new Point(Math.random()*canvas.width, canvas.height);
// The point at which the hair will bend. I set it to the middle of the hair, but you can adjust this.
this.points.bendPoint1 = new Point(this.points.base1.x-this.thickness, this.points.base1.y - this.height / 2)
this.points.bendPoint2 = new Point(this.points.bendPoint1.x, this.points.bendPoint1.y-this.thickness); // complement of bendPoint1 - we use this because the hair has thickness
this.points.base2 = new Point(this.points.base1.x + this.baseWidth, this.points.base1.y) // complement of base1 - we use this because the hair has thickness
}
Hair.prototype.paint = function(mouseX, mouseY, direction) {
ctx.save();
// rotate the the tip of the hair
var tipRotationAngle = Math.atan(Math.abs(this.points.base1.y - mouseY)/Math.abs(this.points.base1.x - mouseX));
// if the mouse is on the other side of the hair, adjust the angle
if (mouseX < this.points.base1.x) {
tipRotationAngle = Math.PI - tipRotationAngle;
}
// if the mouse isn't close enough to the hair, it shouldn't affect the hair
if (mouseX < this.points.base1.x - this.height/2 || mouseX > this.points.base1.x + this.height/2 || mouseY < this.points.base1.y - this.height || mouseY > this.points.base1.y) {
tipRotationAngle = Math.PI/2; // 90 degrees, which means the hair is straight
}
// Use the direction of the mouse to as a lazy way to simulate the direction the hair should bend.
// Note that in real life, the direction that the hair should bend has nothing to do with the direction of motion. It actually depends on which side of the hair the force is being applied.
// Figuring out which side of the hair the force is being applied is a little tricky, so I took this shortcut.
// If you run your finger along a comb quickly, this approximation will work. However if you are in the middle of the comb and slowly change direction, you will notice that the force is still applied in the opposite direction of motion as you slowly back off the set of tines.
if ((mouseX < this.points.base1.x && direction == 'right') || (mouseX > this.points.base1.x && direction == 'left')) {
tipRotationAngle = Math.PI/2; // 90 degrees, which means the hair is straight
}
var tipPoint = new Point(this.points.base1.x + this.baseWidth + this.height*Math.cos(tipRotationAngle), this.points.base1.y - this.height*Math.sin(tipRotationAngle));
ctx.beginPath();
ctx.moveTo(this.points.base1.x, this.points.base1.y); // start at the base
ctx.bezierCurveTo(this.points.base1.x, this.points.base1.y, this.points.bendPoint1.x, this.points.bendPoint1.y, tipPoint.x, tipPoint.y); // draw a curve to the tip of the hair
ctx.bezierCurveTo(tipPoint.x, tipPoint.y, this.points.bendPoint2.x, this.points.bendPoint2.y, this.points.base2.x, this.points.base2.y); // draw a curve back down to the base using the complement points since the hair has thickness.
ctx.closePath(); // complete the path so we have a shape that we can fill with color
ctx.fillStyle='rgb(0,0,0)';
ctx.fill();
ctx.restore();
}
// I used global variables to keep the example simple, but it is generally best to avoid using global variables
window.canvas = document.getElementById('myCanvas');
window.ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(200,255,255)'; // background color
window.hair = [];
window.prevClientX = 0;
for (var i = 0; i < 100; i++) {
hair.push(new Hair());
}
// initial draw
ctx.fillRect(0,0,canvas.width,canvas.height); // clear canvas
for (var i = 0; i < hair.length; i++) {
hair[i].paint(0, 0, 'right');
}
window.onmousemove = function(e) {
ctx.fillRect(0,0,canvas.width,canvas.height); // clear canvas
for (var i = 0; i < hair.length; i++) {
hair[i].paint(e.clientX, e.clientY, e.clientX > window.prevClientX ? 'right' : 'left');
}
window.prevClientX = e.clientX;
}
Made this some time ago, might be useful to some people. Just adjust the variables at the beginning of the code with the values that fits your wishes:
...
Mheight = 1;
height = 33;
width = 17;
distance = 10;
randomness = 14;
angle = Math.PI / 2;
...
Also on http://lucasm0ta.github.io/JsGrass/

Calculate FPS in Canvas using requestAnimationFrame

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

Categories