Closure to set element classes in Javascript with setTimeout - javascript

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);

Related

React Native staggered render

With this code how would I make the render staggered? I want it to render each element one by one rather than just rendering all at once. I've tried setTimeout but don't know how to implement it or whether its even the right way to do it.
renderSelected() {
var temp=[];
for (var i = 0; i < 11; i++) {
temp.push(this.selected(i))
}
return temp;
}
selected(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}
Update based on the answer but it still doesn't work. The code in the answer was too different since this is React Native.
renderLater(i) {
TimerMixin.setTimeout(() => {
this.selected(i);
}, 100);
}
renderSelected() {
var temp=[];
for (var i = 0; i < 11; i++) {
temp.push(this.renderLater(i))
}
return temp;
}
selected(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}
Based on the code, the problem is that you return temp which actually contains nothing, since renderLater returns nothing.
A solution is to create an element with a state, and depending on the state your render one or more elements. This is similar to the timer element on the reactjs page, where the state is updated every second triggering a new rendering. Instead of changing the ticker every second, you can increase a counter and in renderSelected() display all the elements up to that counter. There is no renderLater, just a this.setState() called regularly triggering a new rendering with a different number of elements.
var MyClass = React.createClass({
getInitialState: function() {
return {counter: 0};
},
tick: function() {
if (this.state.counter >= 10) return;
this.setState({counter: this.state.counter + 1});
},
componentDidMount: function() {
this.interval = setInterval(() => {this.tick();}, 50);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
var temp=[];
for (var i = 0; i <= 10 && i <= this.state.counter; i++) {
temp.push(this.selected(i))
}
return temp;
},
selected: function(number) {
return (<View key={number} style={styles.normal} >
<Text>{number}</Text>
</View>);
}
});
ReactDOM.render(<MyClass />, mountNode);
Live demo
You can also instead create all the elements separately from the start with each an empty render() function in the beginning and have them display something when enough time is elapsed. For that, you can still use the x.setState() function for each separate element to trigger a new rendering. This avoids erasing and redrawing already drawn elements at each tick.
Old answer:
You can do a global queue for delayed stuff.
var queue = [];
/* executes all element that have their delay elapsed */
function readQueue() {
var now = (new Date()).getTime();
for (var i = 0; i < queue.length; i++) {
if (queue[i][0] <= now) {
queue[i][1]();
} else {
if(i != 0) queue = queue.slice(i);
return;
}
}
queue = [];
}
/* Delay is in milliseconds, callback is the function to render the element */
function addQueue(delay, callback) {
var absoluteTime = (new Date()).getTime() + delay;
for (var i = 0; i < queue.length; i++) {
if (absoluteTime < queue[i][0]) {
queue.splice(i, 0, [absoluteTime, callback]);
return;
}
}
queue.push_back([absoluteTime, callback]);
}
var queueTimer = setInterval(readQueue, 10); //0.01s granularity
With that queue, if you want to render something some elements every 50ms later, then you can just do:
function renderElementLater(time, index) {
/* render */
addQueue(time, function(){ReactDOM.render(selected(index), mountNode);}):
}
for (var i = 0; i <= 10; i++) {
renderElementLater(50*i, i);
}
You can change the granularity (when the queue is read and checked) to something even less than every 10ms for finer control.
Although, I don't see what's the problem with the setTimeout. You could just do:
function renderElementLater(time, index) {
/* render */
setTimeout(function(){ReactDOM.render(selected(index), mountNode);}, time):
}
for (var i = 0; i <= 10; i++) {
renderElementLater(50*i, i);
}
Maybe your problem came that if you want to use the value of a variable that changes in a callback created in a loop, then it needs to be enclosed by another function (like I did by creating the new function renderElementLater instead of directly putting function code there).

Javascript: Using changing global variables in setTimeout

I am working on Javascript and using firefox scratchpad for executing it. I have a global index which I want to fetch inside my setTimeout (or any function executed asynchronously). I can't use Array.push as the order of data must remain as if it is executed sequentially. Here is my code:-
function Demo() {
this.arr = [];
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);
Here, I wanted my d.arr to have 0 - 9 indexes, all having 'I am John!', but only 9th index is having 'I am John!'. I thought, saving this.counter into index local variable will take a snapshot of this.counter. Can anybody please help me understand whats wrong with my code?
The problem in this case has to do with scoping in JS.
Since there is no block scope, it's basically equivalent to
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
Of course, since the assignment is asynchronous, the loop will run to completion, setting index to 9. Then the function will execute 10 times after 100ms.
There are several ways you can do this:
IIFE (Immediately invoked function expression) + closure
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout((function (i) {
return function(){
self.arr[i] = 'I am John!';
}
})(index), 100);
}
};
Here we create an anonymous function, immediately call it with the index, which then returns a function which will do the assignment. The current value of index is saved as i in the closure scope and the assignment is correct
Similar to 1 but using a separate method
this.createAssignmentCallback = function (index) {
var self = this;
return function () {
self.arr[index] = 'I am John!';
};
};
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(this.createAssignmentCallback(index), 100);
}
};
Using Function.prototype.bind
this.setMember = function() {
for(; this.counter < 10; this.counter++){
setTimeout(function(i){
this.arr[i] = 'I am John!';
}.bind(this, this.counter), 100);
}
};
Since all we care about is getting the right kind of i into the function, we can make use of the second argument of bind, which partially applies a function to make sure it will be called with the current index later. We can also get rid of the self = this line since we can directly bind the this value of the function called. We can of course also get rid of the index variable and use this.counter directly, making it even more concise.
Personally I think the third solution is the best.
It's short, elegant, and does exactly what we need.
Everything else is more a hack to accomplish things the language did not support at the time.
Since we have bind, there is no better way to solve this.
The setTimeout doesn't have a snapshot of index as you are expecting. All of the timeouts will see the index as the final iteration because your loop completes before the timeouts fire. You can wrap it in a closure and pass index in, which means the index in the closure is protected from any changes to the global index.
(function(index){
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
})(index);
The reason is that by the time settimeout is started, the for loop is finished executing the index value is 9 so all the timers are basically setting the arr[9].
The previous answer is correct but the source code provided is wrong, there is a mistyping elf in place of self . The solutions works.
An other way , without a closure , is to just add the index parameter to the function declaration in the setTimeout statement
function Demo() {
this.arr = new Array();
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}(index), 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);

setInterval delays

I'm trying to execute a rotating banner (Calling it through an array). I set an interval but the image only shows after 10 seconds (10000) and only then begins the rotation. I removed the cluttered HTML of the array, but here's the rest of it:
var current = 0;
var banners = new Array();
banners[0]=;
banners[1]=;
banners[2]=;
banners[3]=;
var myTimeout = setInterval("rotater()",10000);
function rotater() {
document.getElementById("placeholderlayer").innerHTML=banners[current];
if(current==banners.length-1){
current = 1;
}else{
current += 1;
}
}
window.onload = rotater();
window.onload = rotater;
is the correct syntax. You don't want to call the function. However, the bulletproof solution is rather this:
onload = function() {
rotater();
window.myTimeout = setInterval(rotater, 10000); // Never pass a string to `setInterval`.
};
ProTip™: Don't use new Array(), use an array literal. For example, this:
var arr = new Array();
arr[0] = 'Hello';
arr[1] = 'world!';
Should be written as:
var arr = ['Hello', 'world!'];
Just a comment:
Instead of:
if(current==banners.length-1) {
current = 1;
} else {
current += 1;
}
you can do:
current = ++current % banners.length;

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

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);

How can I stop this blinking effect?

I have a Javascript function that loops to create a blinking effect by replacing a button image.
function blinkit() {
intrvl = 0;
for (nTimes = 0; nTimes < 500; nTimes++) {
intrvl += 1000;
t = setTimeout("document.getElementById('imgshowreport').src='report_icon.png';",intrvl);
intrvl += 1000;
t = setTimeout("document.getElementById('imgshowreport').src='report_icon2.png';",intrvl);
}
}
This serves the purpose but when the button is clicked I want the blinking to stop. How can I do this without the whole page being refreshed?
Using break to terminate the loop is useless because your loop will already have concluded and set all the timeouts before the user can click the button.
It would be better to use setInterval once rather than queueing up hundreds of individual timeouts. Then you can kill the interval in one fell swoop.
You should also not use string code, but a function:
var myInterval = null;
function blinkit() {
var i = 0;
myInterval = setInterval(function() {
document.getElementById('imgshowreport').src = (i % 2) ? 'report_icon2.png' : 'report_icon.png';
i++;
}, 1000);
}
document.getElementById('imgshowreport').onclick = function() {
if (myInterval != null) {
clearInterval(myInterval);
myInterval = null;
document.getElementById('imgshowreport').src = 'report_icon.png';
}
};
I haven't tested it for typos, but the basic logic is right.
You can use the break syntax
break;
or
nTimes = 500;
Try break
http://www.w3schools.com/js/js_break.asp

Categories