The fine folks over at the JQuery Google Group helped me solve this problem.
I wanted to create an animation with JQuery’s animate() function, but I did not want to use setTimeout(). My reasoning for this was that setTimeout() can only call functions that are within the global namespace, and I try to avoid anything global for all the well-documented reasons. I thought about using call() to change the invocation context, but setTimeout.call(object) will not work, because setTimeout() returns void.
So I was going to use the callback within animate() to synchronously loop back and continue the animation. But I could not find a way to reference the original context of the function where animate() gets called. Perhaps some code would explain this better…
This is what I was trying to do:
$(document).ready(function(){
//control toggle
var startStop = 1;
//object for creating animated elements that fade in or out while moving to a random point.
var Animation = function(elementID){
//opa is opacity at the end of the fade
this.opa = 1;
this.floatAround = function(){
var top = Math.random() * $('#content').height();
var left = Math.random() * $('#content').width();
$(elementID).animate(
{ opacity:(this.opa),
marginTop:top+'px',
marginLeft:left+'px'
},
1000,
'linear',
this.callback
);
this.opa = this.opa > 0 ? 0 : 1; //toggle: fade in or fade out
};
this.callback = function(){
//alert(this); //test
//this test will show that
//"this" refers to the HTML Element that animate() works on.
//That isn't what we want.
//We want to refer to the instance of the Animation object
//so we can call floatAround() again.
if(startStop == 1){
this.floatAround(); //won't work. see above.
}
};
};
//user may click on the containing div to stop the animation.
$('#content').toggle(
function(){
startStop = 0;
},
function(){
startStop = 1;
}
);
//start the animation
var floater = new Animation('#animate_me0'); //create a new Animation object
floater.floatAround(); //cause the the object to start floating around
});
So after some discussion on the JQuery Google Group, a kind soul named mkmanning set me straight. All I had to do was to save a reference (var _this = this;) to the Animation object inside the Animation object, and then this.callback would be able to refer to it (with “_this”). Here is the revised code:
$(document).ready(function(){
//control toggle
var startStop = 1;
//object for creating animated elements that fade in or out while moving to a random point.
var Animation = function(elementID){
//store a reference to the instance of the Animation object,
//so we can invoke floatAround from the callback.
var _this = this;
//opa is opacity at the end of the fade
this.opa = 1;
this.floatAround = function(){
var top = Math.random() * $('#content').height();
var left = Math.random() * $('#content').width();
$(elementID).animate(
{ opacity:(this.opa),
marginTop:top+'px',
marginLeft:left+'px'
},
1000,
'linear',
this.callback
);
this.opa = this.opa > 0 ? 0 : 1; //toggle: fade in or fade out
};
this.callback = function(){
if(startStop == 1){
_this.floatAround(); //loops! works great!
}
};
};
//user may click on the containing div to stop the animation.
$('#content').toggle(
function(){
startStop = 0;
},
function(){
startStop = 1;
}
);
//start the animation
var floater = new Animation('#animate_me0');
floater.floatAround();
});
So I learned some things about animate(), callbacks, invocation contexts, and also about call()…
When call() won’t work:
1. the animate() callback must be provided as a reference to a function, rather than as the invocation of a function. That is, the arguments to animate must appear like this:
$(‘#myElement’).animate({cssIs:awesome}, milliseconds, easing, callback);
and NOT:
$(‘#myElement’).animate({cssIs:awesome}, milliseconds, easing, callback());
Also note that:
callback(), callback(arg1, arg2) and callback.call(context) are all invocations of the callback function, rather than references to it, so they will execute before the animate() function.
2. call() cannot be invoked on a function when the return value is void. So you can’t call setTimeout() with call(), for example.