How to control animation speed (requestAnimationFrame)? - javascript

I change the text color with requestAnimationFrame(animate); function:
requestAnimationFrame(animate);
function animate(time){
... // change text color here
if (offset_s < offset_e) {requestAnimationFrame(animate);}
}
offset_s and offset_s indicates start and end positions of the text for color change. In some cases the animation should last for 2 seconds, but in order cases - for 5 seconds, but offset_e - offset_s could be the same in these two cases. What can I do to control the speed of animation based on given time in seconds/milliseconds?

From the tags of the question i can only see that you animate something drawn on canvas and thats why u cannot use css-animation or jquery-animation.
You have to control the length of the animation by calculating the time difference.
u can do it similar to this example
function start_animate(duration) {
var requestID;
var startTime =null;
var time ;
var animate = function(time) {
time = new Date().getTime(); //millisecond-timstamp
if (startTime === null) {
startTime = time;
}
var progress = time - startTime;
if (progress < duration ) {
if(offset_s < offset_e){
// change text color here
}
requestID= requestAnimationFrame(animate);
}
else{
cancelAnimationFrame(requestID);
}
requestID=requestAnimationFrame(animate);
}
animate();
}
trigger your animation and call start_animate(2000) //duration in millisecond 1000=1 sec

You should separate concerns clearly.
Have a single requestAnimationFrame running, which computes the current animation time and calls every update and draw related functions.
Then your animations would be handled by a function (or class instance if you go OOP) that deals with the current animation time.
Just some direction for the code :
var animationTime = -1;
var _lastAnimationTime = -1;
function launchAnimation() {
requestAnimationFrame(_launchAnimation);
}
function _launchAnimation(time) {
animationTime = 0;
_lastAnimationTime = time;
requestAnimationFrame(animate);
}
function animate(time){
requestAnimationFrame(animate);
var dt = time - _lastAnimationTime ;
_lastAnimationTime = time;
animationTime += dt;
// here call every draw / update functions
// ...
animationHandler.update(animationTime);
animationHandler.draw(context);
}
To start your 'engine', just call launchAnimation then you'll have a valid animationTime and dt to deal with.
I'd make animationHandler an instance of an AnimationHandler class, that allows to add/remove/update/draw animations.

I suggest to use setInterval function in JavaScript.
requestAnimationFrame really needs some 'ugly' calculations. Don't
believe me? Scroll up, you will see...
So, to make setInterval function as handy as rAF(requestAnimationFrame) store the function inside of variable. Here is an example:
var gameLoop = setInterval(function() {
update();
draw();
if (gameOver)
clearInterval(gameLoop);
}, 1000/FPS);
given way, you can control your FPS and pick correct velocity for your objects.

I typically do something like
es6
constructor() {
this.draw();
}
draw() {
const fps30 = 1000 / 30;
const fps60 = 1000 / 60;
window.requestAnimationFrame(() => {
setTimeout(this.draw.bind(this), fps30);
});
}
es5
function DrawingProgram() {
this.draw();
}
DrawingProgram.prototype.draw = function() {
var fps30 = 1000/30;
var fps60 = 1000/60;
var self = this;
window.requestAnimationFrame(function() {
window.setTimeout(function() {
self.draw(); // you could also use apply/call here if you want
}, fps30)
});
}

Related

requestAnimationFrame keeps running while its not called

I am developing a js game and for the update() and draw() method I use requestAnimationFrame. ( I know i should use setInterval for the update). I have a time variable that uses the deltaTime and like that i update the game. When i stop the requsetanimationframe it keeps count again from the stop time. I used the cancelAnimationFrame but it doesnt work. Can someone help me fix the time issue.
let lastTime = 0, reqFrames = 0;
window.restartGame = () => {
window.game.resetAll();
lastTime = 0;
animate(0);
}
function animate(timeStamp){
const deltaTime = timeStamp - lastTime;
lastTime = timeStamp;
ctx.clearRect(0, 0, canvas.width, canvas.height);
window.game.update(deltaTime); // Here is where i update the game.time
window.game.draw(ctx);
if (!game.gameOver) {
window.game.reqFrames = requestAnimationFrame(animate);
}else{
}
}
animate(0);

How can I make objects appear at intervals that decrease over time in p5.js

I'll like to make some elements in an array appear at different times, the time will be decreasing over time (so the elements will appear faster and faster). I had tried with setTimeout and setInterval with no luck, I think it is because I'm looping through the array.
Here's a simple p5.js example that should hopefully point you in the right direction.
I'm calling the setTimeout function for every object on initialisation. The delay of the timeout is incremented by a value incrementor which decreases each iteration.
let circles = [];
let count = 100;
let incrementor = 500;
let delay = 500;
function setup() {
createCanvas(400, 400);
for (let i = 0; i < count; i++) {
let circle = {
x: random(width),
y: random(height),
show: false,
}
incrementor *= 0.9;
delay += incrementor;
setTimeout(() => circle.show = true, delay);
circles.push(circle);
}
}
function draw() {
background(220);
noStroke()
fill(0, 128, 128);
for (let circle of circles) {
if (circle.show) {
ellipse(circle.x, circle.y, 10, 10);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
You cannot achieve that via intervals. Yet, it's possible with recursive timeouts. For example, let's say that the time is decreasing by 2x:
const initialDuration = 10000;
function someAnimation(duration) {
// Your animation code here...
setTimeout(() => someAnimation(duration / 2), duration);
}
someAnimation(initialDuration);

Consistent FPS in frame by frame video with <canvas>

I'm trying to display precisely enough a video that I can stop on or jump to a specific frame. For now my approach is to display a video frame by frame on a canvas (I do have the list of images to display, I don't have to extract them from the video). The speed doesn't really matter as long as it's consistent and around 30fps. Compatibility somewhat matters (we can ignore IE≤8).
So first off, I'm pre-loading all the images:
var all_images_loaded = {};
var all_images_src = ["Continuity_0001.png","Continuity_0002.png", ..., "Continuity_0161.png"];
function init() {
for (var i = all_images_src.length - 1; i >= 0; i--) {
var objImage = new Image();
objImage.onload = imagesLoaded;
objImage.src = 'Continuity/'+all_images_src[i];
all_images_loaded[all_images_src[i]] = objImage;
}
}
var loaded_count = 0;
function imagesLoaded () {
console.log(loaded_count + " / " + all_images_src.length);
if(++loaded_count === all_images_src.length) startvid();
}
init();
and once that's done, the function startvid() is called.
Then the first solution I came up with was to draw on requestAnimationFrame() after a setTimeout (to tame the fps):
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext("2d");
var video_pointer = 0;
function startvid () {
video_pointer++;
if(all_images_src[video_pointer]){
window.requestAnimationFrame((function (video_pointer) {
ctx.drawImage(all_images_loaded[all_images_src[video_pointer]], 0, 0);
}).bind(undefined, video_pointer))
setTimeout(startvid, 33);
}
}
but that felt somewhat slow and irregular...
So second solution is to use 2 canvases and draw on the one being hidden and then switch it to visible with the proper timing:
var canvas = document.getElementsByTagName('canvas');
var ctx = [canvas[0].getContext("2d"), canvas[1].getContext("2d")];
var curr_can_is_0 = true;
var video_pointer = 0;
function startvid () {
video_pointer++;
curr_can_is_0 = !curr_can_is_0;
if(all_images_src[video_pointer]){
ctx[curr_can_is_0?1:0].drawImage(all_images_loaded[all_images_src[video_pointer]], 0, 0);
window.requestAnimationFrame((function (curr_can_is_0, video_pointer) {
ctx[curr_can_is_0?0:1].canvas.style.visibility = "visible";
ctx[curr_can_is_0?1:0].canvas.style.visibility = "hidden";
}).bind(undefined, curr_can_is_0, video_pointer));
setTimeout(startvid, 33);
}
}
but that too feels slow and irregular...
Yet, Google Chrome (which I'm developing on) seems to have plenty of idle time:
So what can I do?
The Problem:
Your main issue is setTimeout and setInterval are not guaranteed to fire at exactly the delay specified, but at some point after the delay.
From the MDN article on setTimeout (emphasis added by me).
delay is the number of milliseconds (thousandths of a second) that the function call should be delayed by. If omitted, it defaults to 0. The actual delay may be longer; see Notes below.
Here are the relevant notes from MDN mentioned above.
Historically browsers implement setTimeout() "clamping": successive setTimeout() calls with delay smaller than the "minimum delay" limit are forced to use at least the minimum delay. The minimum delay, DOM_MIN_TIMEOUT_VALUE, is 4 ms (stored in a preference in Firefox: dom.min_timeout_value), with a DOM_CLAMP_TIMEOUT_NESTING_LEVEL of 5.
In fact, 4ms is specified by the HTML5 spec and is consistent across browsers released in 2010 and onward. Prior to (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2), the minimum timeout value for nested timeouts was 10 ms.
In addition to "clamping", the timeout can also fire later when the page (or the OS/browser itself) is busy with other tasks.
The Solution:
You would be better off using just requestAnimationFrame, and inside the callback using the timestamp arguments passed to the callback to compute the delta time into the video, and drawing the necessary frame from the list. See working example below. As a bonus, I've even included code to prevent re-drawing the same frame twice.
Working Example:
var start_time = null;
var frame_rate = 30;
var canvas = document.getElementById('video');
var ctx = canvas.getContext('2d');
var all_images_loaded = {};
var all_images_src = (function(frames, fps){//Generate some placeholder images.
var a = [];
var zfill = function(s, l) {
s = '' + s;
while (s.length < l) {
s = '0' + s;
}
return s;
}
for(var i = 0; i < frames; i++) {
a[i] = 'http://placehold.it/480x270&text=' + zfill(Math.floor(i / fps), 2) + '+:+' + zfill(i % fps, 2)
}
return a;
})(161, frame_rate);
var video_duration = (all_images_src.length / frame_rate) * 1000;
function init() {
for (var i = all_images_src.length - 1; i >= 0; i--) {
var objImage = new Image();
objImage.onload = imagesLoaded;
//objImage.src = 'Continuity/'+all_images_src[i];
objImage.src = all_images_src[i];
all_images_loaded[all_images_src[i]] = objImage;
}
}
var loaded_count = 0;
function imagesLoaded () {
//console.log(loaded_count + " / " + all_images_src.length);
if (++loaded_count === all_images_src.length) {
startvid();
}
}
function startvid() {
requestAnimationFrame(draw);
}
var last_frame = null;
function draw(timestamp) {
//Set the start time on the first call.
if (!start_time) {
start_time = timestamp;
}
//Find the current time in the video.
var current_time = (timestamp - start_time);
//Check that it is less than the end of the video.
if (current_time < video_duration) {
//Find the delta of the video completed.
var delta = current_time / video_duration;
//Find the frame for that delta.
var current_frame = Math.floor(all_images_src.length * delta);
//Only draw this frame if it is different from the last one.
if (current_frame !== last_frame) {
ctx.drawImage(all_images_loaded[all_images_src[current_frame]], 0, 0);
last_frame = current_frame;
}
//Continue the animation loop.
requestAnimationFrame(draw);
}
}
init();
<canvas id="video" width="480" height="270"></canvas>

HTML Canvas animation wont run on mobile devices

The canvas animation runs effectively on web browsers but when testing on mobile browsers with iPad and iPhone the animation never starts. It just simply displays the background image. There are no error messages given.
The animation is basically an image that moves from offscreen on the left hand side of the canvas and stops when it reaches 75% of the canvas width.
Heres the code
<script>
window.addEventListener("load", eventWindowLoaded, false);
function eventWindowLoaded () {
start();
}
function canvasSupport () {
return Modernizr.canvas;
}
var canvas = document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var image1 = new Image();
image1.onload = function() {
ctx.clearRect(0, 0, 600, 400);
ctx.drawImage(image1, 0, 0);
}
image1.src="images/oven.jpg";
ctx.fillStyle = image1;
var currentX=cw;
var continueAnimating=true;
var nextMoveTime,maxMoves;
var expX = 50;
var expY = 200;
var image2 = new Image();
image2.onload=start;
image2.src="images/pies.png";
var image = new Image();
image.onload=start;
image.src="images/pies.png";
function start(){
maxMoves=(cw+image.width)*0.5;
nextMoveTime=performance.now();
requestAnimationFrame(animate);
function animate(currentTime){
if(continueAnimating){ requestAnimationFrame(animate); }
if(currentTime<nextMoveTime){return;}
nextMoveTime=currentTime; // + delay;
ctx.drawImage(image,currentX,193);
if(--currentX<-image.width){ currentX=cw; }
if(--maxMoves<0){continueAnimating=false;}
}
}
</script>
So the issue comes from your use of performance.now, which is not always implemented, especially on mobile devices where the power drain of a precise timer is too high.
Just use the time provided with the requestAnimationFrame : on accurate browsers/devices, it will use the sub-millisecond accuracy, otherwise it will have only millisecond accuracy.
(accurate = Chrome desktop for sure,... others ???)
I'll let you see below how i use the time of rAF to build the current 'dt' = elapsed time since last frame, and 'applicationTime' = time elapsed in the application (not counting when you tabbed out the app).
A secondary benefit of this method is that you can change easily the application speed to have 'bullet-time' or speed up (or even rewind if speed is <0).
fiddle is here :
http://jsfiddle.net/gamealchemist/KVDsc/
// current application time, in milliseconds.
var applicationTime = 0;
// scale applied to time.
// 1 means no scale, <1 is slower, >1 faster.
var timeSpeed = 1;
// after launchAnimation is called,
// draw/handleInput/update will get called on each rAF
function launchAnimation() {
requestAnimationFrame(_launchAnimation);
}
// ------------- Private methods ----------------
function _launchAnimation(now) {
_lastTime = now;
applicationTime = 0
requestAnimationFrame(_animate);
}
// ----------------------------------------------
// Animation.
// Use launchAnimate() to start the animation.
// draw, handleInput, update will be called every frame.
// ----------------------------------------------
function _animate(now) {
requestAnimationFrame(_animate);
// _______________________
var dt = now - _lastTime;
if (dt < 12) return; // 60 HZ max
if (dt > 200) dt = 16; // consider 1 frame elapse on tab-out
_lastTime = now;
dt *= timeSpeed;
applicationTime += dt;
// _______________________
handleInput(); // ...
// update everything with this frame time step.
update(dt);
// draw everything
draw();
}
var _lastTime = 0;
(( Notice that to handle most gracefully the tab-out, you have to handle the blur event, cancel the rAF, then set-it again on focus. ))
var now =
( typeof performance === 'object' && 'now' in performance )
? function() { return performance.now(); }
: function() { return ( new Date ).getTime(); };

update animation based on time in javascript(independent of frames)

I am implementing a simple animation using javascript in canvas.An image updates its position based on the time elapseddt between each frame. Here is the code
var lastTime=0;
var speed=100;
mySprite = function() {
this.pos=[0,0];
}
function spritePosition(dt) {
for (i=0; i < Stuff.sprite.length;i++) {
Stuff.sprite[i].pos[0] += speed*dt;
}
}
function animate(){
var canvas=document.getElementById('mycan');
var context=canvas.getContext('2d');
var now = Date.now();
var dt = (now - lastTime) / 1000.0;
//clear
context.clearRect(0, 0, canvas.width, canvas.height);
//update
spritePosition(dt);
updateSprite();
//render
background(canvas,context);
draw(context);
lastTime = now;
//request new Frame
requestAnimFrame(function() {
animate();
});
}
window.onload=function(){
init();
animate();
}
dt
values are in the range 0.3-0.5
But the line
Stuff.sprite[i].pos[0] += speed*dt;##
assigns position values as 136849325664.90016.
Please help.
You initialize lastTime to 0 - so the initial delta is veeeeery long (as of today almost 45 years!). You should make sure to catch the very first run (compare to 0? or initialize lastTime with Date.now()) and treat it separately, possibly setting dt to 0.

Categories