How can I end a requestanimationFrame with a function? [duplicate] - javascript

I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.

It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});

Related

Is there a way to cancel requestAnimationFrame without a global variable?

I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});

Setting up a timer event in a prototype using jQuery

I am having a hard time getting a countdown timer working as I don't know what I am doing wrong. I am trying to setup a countdown timer using jQuery in a prototype.
The main problem I see so far is at the setInterval:
_self.counter = setInterval(_self.runTimer(_self),1000);
When I don't pass in the "this" I get NaN but when I do the countdown only happens once and then stops.
Here is my JSFiddle work so far:
http://jsfiddle.net/f9GN7/
Thank you in advance.
I've modified a little of your code, I changed setInterval to setTimeout.
var timer_code = function(){
this.counter;
this.timeCountDown = 30;
}
timer_code.prototype = {
init : function(){
var _self = this;
$('#start').on('click',function(e){
_self.setTimer();
});
},
setTimer : function(){
var _self = this;
// _self.counter = setInterval(_self.runTimer(_self),1000);
var timerLoop = function(){
if(_self.timeCountDown > 0){
_self.runTimer();
setTimeout(timerLoop, 1000);
}
};
timerLoop();
},
runTimer : function(){
var _self = this;
_self.timeCountDown--;
if(_self.timeCountDown <= 0){
// clearInterval(_self.counter);
$('#timer').html("DONE");
return;
}
$('#timer').html(_self.timeCountDown);
console.log(_self.timeCountDown);
}
}
var timer = new timer_code();
timer.init();
http://jsfiddle.net/f9GN7/1/
setInterval gets a function reference as its first parameter ..
This function may not return a function object, the function call you just passed needs to be called in the scoope of a closure
Keeping your code with just a few modifications :
setTimer: function(){
if(this.counter)
clearInterval(this.counter); // timer may have already been launched, it may need to be cleared if its value is an integer and is != 0
this.counter = setInterval(
(function (ref) {
return function () {
ref.runTimer();
}
})(this),
1000);
}
See Fiddle Here

Javascript: destructor or something like that

I've created this little object that's pretty handy when it comes to intervals and works pretty good as an animation frame, but it has this little thing about it. If the reference to the instance is lost, the interval keeps going.
function Interval(callback, interval){
var timer = null;
this.start = function(){
if(!timer) timer = setInterval(callback, interval);
};
this.stop = function(){
clearInterval(timer);
timer = null;
};
this.changeSpeed = function(a){
interval = a;
this.stop();
this.start();
}
this.destroy = function(){
delete this;
}
}
Obviously if javascript has no destruct method I can't track when to stop the interval, so I figured I should create a destroy method, but I'm not really sure if I can destroy the instance from within the object. It makes sense but... Any help is appreciated!
How about doing something like this:
function Interval(callback, interval){
var self = this;
var timer = null;
this.start = function(){
if(!timer) timer = setInterval(function() {
callback(self)
}, interval);
};
this.stop = function(){
clearInterval(timer);
timer = null;
};
this.changeSpeed = function(a){
interval = a;
this.stop();
this.start();
}
this.destroy = function(){
this.stop();
}
}
Now at least when the callback is called it will pass a reference to the your object and the callback will at least have a chance to stop it if they don't need it anymore.
The trick here is to use a closure to ensure you can still reference the object when the interval expires (hence the self variable).
So now I could do something like this:
var foo = new Interval(function(i) {
// check if my interval is still needed
if (dontNeedItAnymore) {
i.destroy(); // or just .stop()
}
else {
// do whatever
}
}, 1000);
foo = null; // whoops, lost the reference, but the callback will still be able to reference it

Referencing Non-Global Variable for Timer JS

I have this function.
function changeFrame(){
var time = setInterval(start, 250);
}
and I want to stop it from firing in another function, but haven't been able to figure out how to do it.
Do you mean this?
function changeFrame(){
var time = setInterval(function() {
// Do stuff
}, 250);
}
Think it's in the comments.
Ok amended the fiddle to do what you want. I made time a global var. Call clearInterval in stop with the global var http://jsfiddle.net/QNWF4/3/
In order to call clearInterval you need to have the handle returned by setInterval. That means something will either be global to the page or global to a containing function in which your script resides.
function Timer()
{
var handle = null;
this.start = function (fn,interval) {
handle = setInterval(fn,interval);
};
this.stop = function ()
{
if (handle) { clearInterval(handle); handle = null; }
};
return this;
}

Class variables in JavaScript and setInterval

Since I need to pass an anonymous function to setInterval if I want parameters, I tried using the below code. Originally I had it calling this.countUp, but as that returned NaN I did some reading and found the .call(this) solution on SO. However, when I combined that with the anonymous function (which I admit I'm a bit foggy on), I'm now getting TypeError: this.countUp is undefined in Firebug.
I suppose I don't need to make count accessible, nor the playBeep method, but let's pretend I wanted to so that I can understand what I'm doing wrong with this code.
function workout() {
var beep = new Audio("beep1.wav");
this.timerWorkout; //three timers in object scope so I can clear later from a different method
this.timerCounter;
this.timerCoolDown;
this.count = 0;
this.startWorkout = function() {
alert(this.count);
this.timerWorkout = setTimeout(this.playBeep, 30 * 1000); //workout beep - 30 seconds
this.timerCounter = setInterval(function() {this.countUp.call(this)}, 1000); //on screen timer - every second
}
this.startCoolDown = function() {
this.timerCoolDown = setTimeout(this.playBeep, 10 * 1000); //cooldown beep - 10 seconds
}
this.playBeep = function() {
beep.play(); //plays beep WAV
}
this.countUp = function() {
this.count++;
document.getElementById("counter").innerHTML = this.count;
}
}
var workout1 = new workout()
Inside startWorkout use bind(this) :
this.timerCounter = setInterval(function() {this.countUp()}.bind(this), 1000);
What happens is setInterval is changing the value of this inside the function you provide for it to call. You need to store this in a separate variable to prevent it from getting overridden.
function workout() {
var self = this;
// ...
this.startWorkout = function() {
alert(this.count);
this.timerWorkout = setTimeout(self.playBeep, 30 * 1000); // this method works
this.timerCounter = setInterval(function() {self.countUp}, 1000); // so does this one
}
}
The reason that the variable scope in js is limited on function. So when you are trying to use this inside a nested function, you get a link to another object. Create a variable var that = this; into a higher-level function, and then use it in any nested function that would refer you to the correct context.

Categories