Manngo
Manngo

Reputation: 16281

requestAnimationFrame Fixed Duration

I am trying to understand one thing about requestAnimationFrame.

In the sample code below, I am running something 60 times, which should match the typical 60 frames per second that you normally get, so it should last 1 second. However, I have deliberately dragged it out by calling a time wasting function.

Clearly, this takes longer (about 5 seconds on my machine).

The question is, how do I go about setting up the animation so that it lasts a fixed duration such as 1 second — presumably this should skip a few frames in the process?

Although example sounds specific, it has a broader context. I would like to create animations such as fading or sliding which have a set duration rather than a set number of frames, since the frame rate is not totally predictable. This would match the behaviour of CSS3 animations.

Thanks

var i=0;
var start=new Date();
var end;

var counter=document.getElementById('counter');
var time=document.getElementById('time');

doit();

function doit() {
  if(i>=60) {
    end=new Date();
    time.innerHTML=end.getTime() - start.getTime();
    return;
  }
  wasteTime();
  counter.innerHTML+=i+' ';
  i++;
  requestAnimationFrame(doit)
}

function wasteTime() {
  for(var i=0;i<100000000;i++);
}
<p id="counter"></p>
<p id="time"></p>

Upvotes: 1

Views: 850

Answers (3)

Manngo
Manngo

Reputation: 16281

Here is a solution to the problem. The trick is to exit when the set duration has elapsed.

The counter in the sample below should not be incremented, but set to a proportionate value. If so, it can also be used correctly to exit.

The other trick is to take advantage of the timestamp parameter of the callback.

Thanks!

/*	HTML
	================================================
	<p id="counter"></p>
	<p id="time"></p>
	================================================ */


var limit=60;
var i=0;
var start=null;

var counter=document.getElementById('counter');
var time=document.getElementById('time');


doit();

function doit(timestamp) {
	if(!start) start=timestamp;
	var lapsed=timestamp-start;
	if(lapsed>=1000) {	//	if(i>=limit) {
		time.innerHTML='<br>'+lapsed;
		return;
	}
	wasteTime();
	counter.innerHTML += i+' ';
	i=lapsed/1000*limit;		//	Rather than increment
	requestAnimationFrame(doit)
}

function wasteTime() {
	for(var i=0;i<100000000;i++);
}
<p id="counter"></p>
<p id="time"></p>

Upvotes: 0

James Thorpe
James Thorpe

Reputation: 32202

setInterval is not the correct solution.

You need to approach this a bit differently, in that your animations need to be aware of how far through the animation they are, and the time at which they began. When the next animation frame comes through from the browser, you can then update the positions of whatever is being animated by the speed with which they are moving multiplied by however long it's been since the last frame. As you say, you can't guarantee on 60fps if you've got long processing going on.

In your example case, you'd need to increment i by a number appropriate to the amount of time passed since the last frame:

var i=0;
var start=new Date();
var last=new Date();
var end;

var counter=document.getElementById('counter');
var time=document.getElementById('time');

doit();

function doit() {
  if(i>=60) {
    end=new Date();
    time.innerHTML=end.getTime() - start.getTime();
    return;
  }
  
  //Rather than just incrementing i by 1, here we
  //look at the difference since the last frame
  //and increment i by an appropriate amount
  var diff = new Date() - last;

  counter.innerHTML+=i+' ';
  i += diff/60;

  requestAnimationFrame(doit)
  last = new Date();
  wasteTime();
}

function wasteTime() {
  for(var i=0;i<100000000;i++);
}
<p id="counter"></p>
<p id="time"></p>

Note: I'm unsure why this is still taking longer than 1 second to run, still looking. It may be how it's interacting with the browser to write out the HTML.

Upvotes: 1

Niddro
Niddro

Reputation: 1725

If you instead want to use a fixed interval between your loops, you're better off using setInterval();

var myInterval = setInterval(doit,1000);

This will initiate the doit()-function each second. To cancel your interval you'll

clearInterval(myInterval);

If you still want to utilize the advantages of using requestAnimationFrame, you can combine it with setInterval

var myInterval = setInterval(function () {
    requestAnimationFrame(doit);
},1000);

Although I'm not sure how the above will play out if you're visiting another tab and the setInterval-function will start to line up all these rAF's, so be aware.

Upvotes: 0

Related Questions