creating a timer using setInterval that can clean up after itself? - javascript

I want to use setInterval to animate a couple things. First I'd like to be able to specify a series of page elements, and have them set their background color, which will gradually fade out. Once the color returns to normal the timer is no longer necessary.
So I've got
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(itvlH);
// but itvlH isn't in scope...?
}
},50);
}
Further complicating the situation is I'd want to be able to have multiple instances of this going on. I'm thinking maybe I'll push the live interval handlers into an array and clean them up as they "go dead" but how will I know when they do? Only inside the interval closure do I actually know when it has finished.
What would help is if there was a way to get the handle to the interval from within the closure.
Or I could do something like this?
function intRun() {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// now I can access an array containing all handles to intervals
// but how do I know which one is ME?
clearInterval(itvlH);
}
}
var handlers = [];
function setFadeColor(nodes) {
var x = 256;
handlers.push(setInterval(intRun,50);
}

Your first example will work fine and dandy ^_^
function setFadeColor(nodes) {
var x = 256;
var itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
clearInterval(itvlH);
// itvlH IS in scope!
}
},50);
}
Did you test it at all?

I've used code like your first block, and it works fine. Also this jsFiddle works as well.

I think you could use a little trick to store the handler. Make an object first. Then set the handler as a property, and later access the object's property. Like so:
function setFadeColor(nodes) {
var x = 256;
var obj = {};
// store the handler as a property of the object which will be captured in the closure scope
obj.itvlH = setInterval(function () {
for (i in nodes) {
nodes[i].style.background-color = "rgb(0,"+(--x)+",0);";
}
if (x <= 0) {
// would like to call
clearInterval(obj.itvlH);
// but itvlH isn't in scope...?
}
},50);
}

You can write helper function like so:
function createDisposableTimerInterval(closure, delay) {
var cancelToken = {};
var handler = setInterval(function() {
if (cancelToken.cancelled) {
clearInterval(handler);
} else {
closure(cancelToken);
}
}, delay);
return handler;
}
// Example:
var i = 0;
createDisposableTimerInterval(function(token) {
if (i < 10) {
console.log(i++);
} else {
// Don't need that timer anymore
token.cancelled = true;
}
}, 2000);

Related

Closure to set element classes in Javascript with setTimeout

I am trying to animate through CSS properties by adding "start" and "end" classNames through javascript. I have defined a bunch of these classes in CSS (for instance starting with opacity: 0 and ending with opacity: 1
I am trying to loop through all of the elements, set a start className on an element, then an end className on the element to trigger the transition. The problem is if I do that how I normally would, by the time the function finishes there would be no actual className change.
Using setTimeout with even no delay is one way to get around this, as was mentioned here but that method does not work in this instance because of my looping. I am using a closure.
Here is my JS code:
var animation = (function () {
var a = {};
a.divIndex = -1;
a.imgIndex = startHolders.length;
a.animIndex = -1;
a.divs = startHolders;
a.run = function () {
if (++a.divIndex === a.divs.length) a.divIndex = 0;
if (++a.animIndex === animations.length) a.animIndex = 0;
if (++a.imgIndex === imageElements.length) a.imgIndex = 0;
imageElements[a.imgIndex].className = animations[a.animIndex]['start'];
setTimeout(function() {
imageElements[a.imgIndex].className = animations[a.animIndex]['end'];
}, 0);
startHolders[a.divIndex].appendChild(imageElements[a.imgIndex]);
};
setInterval(a.run, 1000);
return a;
})();
What I really wanted was to be able to set all those indexes to 0 and use this instead of a (just some placeholder object) but I couldn't do this with the setTimeout because of how it changes the value of this. Another problem here is that if I put the if(++a.index) at the bottom of the code, by the time setTimeout runs the value ofa.index` has changed (I believe) Does anybody know of a workaround here or just a better way?
If you would like access to this inside of the function passed to setTimeout you can use bind.
For example:
var obj = {}
obj.divIndex = "do you wanna build a snowman?";
obj.run = function () {
console.log(this.divIndex);
}
setTimeout(obj.run.bind(obj), 1000);
I'm thinking you might want to do something like this?:
var a = {};
a.divIndex = -1;
a.imgIndex = startHolders.length;
a.animIndex = -1;
a.divs = startHolders;
a.run = function () {
if (++this.divIndex === this.divs.length) this.divIndex = 0;
if (++this.animIndex === animations.length) this.animIndex = 0;
if (++this.imgIndex === imageElements.length) this.imgIndex = 0;
imageElements[this.imgIndex].className = animations[this.animIndex]['start'];
setTimeout(function() {
imageElements[this.imgIndex].className = animations[this.animIndex]['end'];
}.bind(this), 0);
startHolders[this.divIndex].appendChild(imageElements[this.imgIndex]);
};
setInterval(a.run.bind(a), 1000);

Can someone explain this Javascript Singleton code to me?

this file
http://www.iguanademos.com/Jare/docs/html5/Lessons/Lesson2/js/GameLoopManager.js
taken from this site
Here is the code:
// ----------------------------------------
// GameLoopManager
// By Javier Arevalo
var GameLoopManager = new function() {
this.lastTime = 0;
this.gameTick = null;
this.prevElapsed = 0;
this.prevElapsed2 = 0;
I understand the declaration of variables,
and they are used to record the time between frames.
this.run = function(gameTick) {
var prevTick = this.gameTick;
this.gameTick = gameTick;
if (this.lastTime == 0)
{
// Once started, the loop never stops.
// But this function is called to change tick functions.
// Avoid requesting multiple frames per frame.
var bindThis = this;
requestAnimationFrame(function() { bindThis.tick(); } );
this.lastTime = 0;
}
}
I don't understand why he uses var bindThis = this
this.stop = function() {
this.run(null);
}
This function set's gameTick to null, breaking the loop in this.tick function.
this.tick = function () {
if (this.gameTick != null)
{
var bindThis = this;
requestAnimationFrame(function() { bindThis.tick(); } );
}
else
{
this.lastTime = 0;
return;
}
var timeNow = Date.now();
var elapsed = timeNow - this.lastTime;
if (elapsed > 0)
{
if (this.lastTime != 0)
{
if (elapsed > 1000) // Cap max elapsed time to 1 second to avoid death spiral
elapsed = 1000;
// Hackish fps smoothing
var smoothElapsed = (elapsed + this.prevElapsed + this.prevElapsed2)/3;
this.gameTick(0.001*smoothElapsed);
this.prevElapsed2 = this.prevElapsed;
this.prevElapsed = elapsed;
}
this.lastTime = timeNow;
}
}
}
Most of this code is what I don't understand, I can see he is recording the time elapsed between frames, but the rest of the code is lost to me.
On the website he uses the term singleton, which is used to prevent the program trying to update the same frame twice?
I have a bit of experience with the javascript syntax, but the concepts of singleton, and the general goal/function of this file is lost to me.
Why is the above code needed instead of just calling
requestAnimationFrame(function() {} );
The reason he uses bindThis is that he is passing a method into an anonymous function on the next line. If he merely used this.tick(), this would be defined as the context of requestAnimationFrame. He could achieve the same thing by using call or apply.
Singletons are classes that are only instantiated once. This is a matter of practice, and not a matter of syntax - javascript doesn't know what a singleton is. By calling it a "Singleton", he is merely communicating that this is a class that is instantiated only once, and everything that needs it will reference the same instance.

setInterval - How to preserve passed in variable

I did some digging around on SO and could not find exactly what I am trying to achieve.
In simplistic terms I have a function like
function(){
for(i=0;i<10;i++){
setInterval(function(){ alert(i);), 1000)
}
}
What I would expect is 10 setIntervals that would alert 1 to 10 every 1 second, what happens is it would alert 10 always since 'i' is 10 at the end of for loop. How do I pass 'i' to setInterval anonymous function so that I can preserve the value of i in setInterval?
Above was a simplistic version of my actual problem. I am actually trying to do this
var timers = [];
function(obj){
//Clear any intervals
for(i=0;i<timer.length;i++){
clearInterval(timers[i]);
}
// Empty timers Array
timers = [];
for(i in obj){
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if(obj[i] === "Something"){
timers.push(setInterval(function(){
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000)
}
}
}
my_obj in the above code always refers to id = last 'i' in obj.
Do I make sense?
This should do the trick ;)
for(i = 1; i < 11; i++){
(function(local_i){
setInterval(function(){ console.log(local_i); }, 1000 * local_i)
})(i);
}
You must capture the variable in a closure. In your case this is
function capture(x) {
setInterval(function () {
console.log(x);
}, 1000);
}
for (var i = 0; i < 10; i++) {
capture(i);
}
or
function capture(my_obj) {
var id = setInterval(function() {
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000);
return id;
}
for (i in obj) {
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if (obj[i] === "Something") {
timers.push(capture(my_obj));
}
}

Javascript increment not working

Well I did not know what exactly would be a good title for this because it is a most peculiar situation or I'm abnormally dumb.
Here's what im trying to do.
Create a simple <meter> tag which is new in HTML5. The main issue is with my javascript. Im trying to increment the value of the meter tag gradually in my javascript. But somehow it doesn't work the way i want.
JavaScript.
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
setTimeout(function () {
console.log(i);
a.value = i;
}, 250);
}
I'm trying to increase the value of the meter gradually every 250 ms.This doesn't happen. Instead the meter jumps straight to 10.
What interested me was the value of i that i got in the console. I got instances of 10, instead of 1,2,3...10.
Why does this happen?
FIDDLE
It's a JavaScript closures' classic. Here i is an actual reference to the variable, not its copy. After you've iterated through the loop it has the value of 10, that's why all log invocations write 10 to log.
This should work better:
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
setTimeout(function (i) {
return function() {
console.log(i);
a.value = i;
};
}(i), 250 * i);
}
Here the most inner i is the setTimeout's callback argument, not the variable which you've declared in the loop body.
You should read more about closures in JavaScript. When a variable gets closed over, it's the same exact variable, not a copy. Since setTimeout is asynchronous, the whole loop finishes before any of the functions run, therefore the i variable will be 10 everywhere.
DEMO
function incMtrAsync(max, delay, el) {
if (el.value++ < max) {
setTimeout(incMtrAsync.bind(null, max, delay, el), delay);
}
}
incMtrAsync(10, 250, document.getElementById("mtr1"));
The above implementation implements the loop using a recursive approach. Everytime inMtrAsync is called, it checks if the value of the meter reached the max value, and if not, registers another timeout with a callback to itself.
If you want to delay the initial increment as well, just wrap the first call in another timeout.
setTimeout(incMtrAsync.bind(null, 10, 250, document.getElementById("mtr1")), 250);
Nobody used setInterval, so here's my solution ( http://jsfiddle.net/Qh6gb/4/) :
var a = document.getElementById("mtr1");
var i = 0;
var interval = setInterval(function () {
console.log(i);
a.value = ++i;
if (i == 10) {
clearInterval(interval);
}
}, 250);
The problem you describe happens before the asyncronous call to setTimeout in your original version sees a value of 10 for i because that is its value at the moment the callback is executed.
So, this is a problem with the scope of the closure, to make it work you should make it like this:
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
(function (i, a) {
setTimeout(function () {
console.log(i);
a.value = i;
}, 250);
})(i, a);
}
also, since a is always the same, this should be better:
var a = document.getElementById("mtr1");
for (var i = 0; i <= 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
a.value = i;
}, 250);
})(i);
}
If then you want to see the counter "ticking up", this will make it visible:
var a = document.getElementById("mtr1");
for (var i = 0; i <= 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
a.value = i;
}, 1000 * i);
})(i);
}
See http://jsfiddle.net/LDt4d/
It happens because you called setTimeout, which is "asynchronous". So setTimeout is called 10times but after whole loop is done then it is executed. Therefore, i = 10 in each call...
http://jsfiddle.net/Qh6gb/9/
there is the solution:
var i = 1,
meter = document.getElementById("mtr1");
function increase() {
meter.value = i++;
console.log(i);
if(i<=10) {
setTimeout(increase, 250);
}
}
setTimeout(increase, 250);
you can use timeout jquery plugin:. It is easier
However you should calculate your timeout ,
For you ,timeout=250*max=250*10=2500
So
$('meter').timeout(2500);
Demo
Run for loop inside the function instead of declaring a closure in every step of the loop.
JSFIDDLE: http://jsfiddle.net/Qh6gb/3/
var a = document.getElementById("mtr1");
setTimeout(function () {
for (var i = 0; i < 10; i++) {
console.log(i);
a.value = i;
}
}, 250);
I hope I understand right. Please try and tell me if you got solution.
var count = 0;
function increment(){
document.getElementById("meter").value = count;
count++;
if(count ==10)
count=0;
}
setInterval(increment, 250);
Please check with jsFiddle
You're creating multiple functions that are all being set off at the same time.
Multiply the timer by i for correct delay.
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
setTimeout(function () {
console.log(i);
a.value = i;
}, 250 * i);
}

Trying to figure out why function(argument) not console-logging as expected

var interval = window.setInterval(animate, 500);
var i = 5;
function animate() {
if (i > 1) {
i--;
console.log(i);
} else {
window.clearInterval(interval);
}
}
animate();
The above javascript has var i = 5; and console logs the numbers 5, 4, 3, 2, and 1. DEMO fiddle
I wanted to put the starting number into the animate() function as an argument though, so I added an argument to animate(), defined the variable i as solely var i;, and put a number into animate():
var interval = window.setInterval(animate, 500);
var i;
function animate(i) {
if (i > 1) {
i--;
console.log(i);
} else {
window.clearInterval(interval);
}
}
animate(10);
DEMO fiddle
However, this second attempt only spits out the number 9, and doesn't spit out 10, 9, 8, 7, etc.
Does anyone know what I'm doing wrong?
You could instead have the function recursively call itself, and use setTimeout with an anonymous function that passes in the current value of i.
function animate(i) {
if (i > 1) {
i -= 1;
console.log(i);
setTimeout(function() {
animate(i);
}, 500);
}
}
animate(10);
This way, you don't need to manage an interval, and the animation will only be controlled by the animate() method. Cleaner IMO.
Working fiddle: http://jsfiddle.net/UNkhK/
If you still want control over the timeout: You could also extend this guy's functionality by making him into an object.
function Animation(delay) {
this.delay = delay || 500;
this.timeout = null;
}
Animation.prototype.animate = function(i) {
if (i > 1) {
i -= 1;
console.log(i);
var self = this;
this.timeout = setTimeout(function() {
self.animate(i);
}, this.delay);
}
}
Animation.prototype.stop = function() {
clearTimeout(this.timeout);
}
var animation = new Animation(500);
animation.animate(10);
// Testing
setTimeout(function() {
animation.stop();
console.log('Animation stopped prematurely!');
}, 2000);
Working fiddle / demo: http://jsfiddle.net/5dZyd/
Function arguments are local variables that shadow global variables. So if you use the same name i for the argument, you can't access the global variable that has that name. You need to use different variables.
var global_i;
function animate(local_i) {
if (local_i !== undefined) { // Will be undefined when called from setInterval
global_i = local_i;
}
if (global_i > 1) {
global_i--;
console.log(global_i);
} else {
window.clearInterval(interval);
}
}
animate(10);
you didnt use the variable i, you created a new one, in short, i inside the function definition - animate(i) creates a new var i for the function, and doesn't use the global one try
var i = 10;
function animate() {
if (i > 1) {
i--;
console.log(i);
}
/*
rest of your code here
*/
animate();
see javascript scoping
also use setTimeout not setInterval see here
fiddle
You may try this (call the function from within the function itself and pass the variable i)
var interval;
function animate(i) {
if (i > 1) {
i--;
console.log(i);
interval = setTimeout(function(){ animate(i) }, 500);
}
}
animate(10);
FIDDLE.

Categories