Reverse for loop with setTimeout - javascript

I have a li that I want to animate while hovering over.
I try to get the previous li item with ยง(this).prevAll("li") and then use JQuery to change the style.
For this I want to get the IDs of the previous items. My current code looks like this:
$("#somediv li").hover(function()
{
var object = $(this).prevAll("li");
var i;
for (i = object.length - 1; i >= 0; i--) {
dowork(i);
}
function dowork(i) {
setTimeout(function() {
console.log(i);
$("#"+object[i].id+"").addClass("animateprogress");
}, 500 * i);
}
Output of the for loop is 5,4,3,2,1,0 (depending on object length). The timeout output though reverses this to 0,1,2,3,4,5. I don't know why tough. I just want to output 5,4,3,2,1,0 with 500ms delay to style my previous li elements.

You'll have to distinguish between the delay you want and the object index you want. Both are now the same variable i in your function, but they should be somewhat opposite.
So for example do:
$("#somediv li").hover(function() {
var object = $(this).prevAll("li");
var i;
for (i = 0; i < object.length; i++) {
// pass a delay that increases with each iteration,
// and pass an index that decreases with each iteration:
dowork(500 * i, object.length - 1 - i);
}
function dowork(delay, index) {
setTimeout(function() {
console.log(index);
$("#"+object[index].id).addClass("animateprogress");
}, delay);
}
});

try:
setTimeout(animate, (object.length - 1 - i) * 500)
instead.

you can use these codes too
$("#somediv li").hover(function()
{
var object = $(this).prevAll("li");
var i;
for (i = object.length - 1; i >= 0; i--) {
dowork(i,(object.length-i)*500);
}
function dowork(i,d) {
setTimeout(function() {
console.log(i);
$("#"+object[i].id+"").addClass("animateprogress");
}, d);
}
It will give the result you expect.
The issue was on delay time you was adding greater time for greater number which is opposite of your expectation

Related

fail to setTimeout in a for loop

I'm building a simon game. And after each round the player should see the moves he must play in the next round. So i created a function showMoves which flashes the square he has to play. The problem is that the function is not showing anything. Can anyone tell me what did i miss?
// the effect
function flasher(index) {
$(moves[index]).fadeIn(50).fadeOut(50).fadeIn(50).fadeOut(50).fadeIn(100);
}
var interval2;
// show the moves that supposed to be played
function showMoves() {
for (var i = 0; i < moves; i++) {
if (i === 0) {
interval2 = setTimeout(flasher(i), 1000);
} else {
interval2 = setTimeout(flasher(i), (i+1) * 1000);
}
}
}
setTimeout accepts a function as a first parameter. I assume that by calling flasher you tried to avoid this situation. In you case, this should be done like this:
function showMoves() {
for (var i = 0; i < moves; i++) {
if (i === 0) {
interval2 = setTimeout(function(i) {return function() {flasher(i)}}(i), 1000);
} else {
interval2 = setTimeout(function(i) {return function() {flasher(i)}}(i), (i+1) * 1000);
}
}
}
The setTimeout and setInterval are a little diffrent than we think about them.
They are save some event on specified times that will be fired in its times. Because of this they has a problem with loops:
for(i=0;i<3;i++)
{
setTimeout(function(){alert(i)}, i*1000);
}
after ending the loop the browser has 3 jobs to do:
alert(i) after 1 second
alert(i) after 2 seconds
alert(i) after 3 seconds
But what is the value of 'i'. If you are in c# programming after ending the loop 'i' will be disposed and we have not that.
But javascript does not dispose 'i' and we have it yet. So the browser set the current value for i that is 3. because when 'i' reaches to 3 loop goes end. Therefor Your browser do this:
alert(3) after 1 second
alert(3) after 2 seconds
alert(3) after 3 seconds
That is not what we want. But if change the above code to this:
for(i=0;i<3;i++){
(function (index)
{
setTimeout(function () { alert(index); }, i * 1000);
})(i);
}
We will have:
alert(0) after 1 second
alert(1) after 2 seconds
alert(2) after 3 seconds
So as Maximus said you mast make the browser to get value of i currently in loop. in this way:
setTimeout(function(i) {return function() {flasher(i)}}(i), (i+1) * 1000);
i does not leave out until end of loop and must be get value just now.
What I can derive from your code is that moves is an array, but you're using it as if it's an integer in the for loop. And that's why nothing happens at all.
Replace:
for (var i = 0; i < moves; i++) {
With:
for (var i = 0; i < moves.length; i++) {
And you should see things happening.
But you will notice flasher is called immediately, without timeout. And that's because the result of flasher is set to be called, instead of flasher itself.
Other answers here suggest using an wrapper function, but this requires workarounds to correctly pass the index to the function called by setTimeout.
So assuming that it doesn't have to run in IE8 and below, the following is the most concise solution:
setTimeout(flasher.bind(null, i), (i+1) * 1000)
Full working example:
var moves = [1, 2, 3, 4];
function flasher(index) {
console.log('move', moves[index]);
}
var interval2;
// show the moves that supposed to be played
function showMoves() {
for (var i = 0; i < moves.length; i++) {
interval2 = setTimeout(flasher.bind(null, i), (i+1) * 1000);
}
}
showMoves()

Get random element from array, display it, and loop - but never followed by the same element

I wrote a simple enough randomize function that loops through the elements in an array and displays them one after the other.
See it here.
function changeSubTitle() {
var whatAmI = ["Webdesigner", "Drummer", "Techie", "Linguistics student", "Photographer", "Geek", "Coder", "Belgian", "Batman", "Musician", "StackExchanger", "AI student"];
setTimeout(function () {
$(".page-header > h2").animate({
"opacity": 0
}, 700, function () {
$(this).text(whatAmI[Math.floor(Math.random() * whatAmI.length)]);
$(this).animate({
"opacity": 1
}, 700, changeSubTitle);
});
}, 1000);
}
However, obviously it is very well possible that the same element is displayed twice, one immediately after the other. This happens because I randomize the array each time I call the function. How would I prevent an element to be displayed two times right after each other?
I suppose the most straightforward way to do this is to get the randomize function out the loop, run the loop and each time an element is called, remove the index from the array and refill the array when it's empty. A lot of questions on SO consider this problem, but not as specific as mine: I'm not sure how to do this in the loop to display each element.
Stop upvoting please :-D
The following answer is better: https://stackoverflow.com/a/32395535/1636522. The extended discussion between me and Tim enlighten on why you should avoid using my solutions. My answer is only interesting from this point of view, hence, it does not deserve any upvote :-D
You could save the last integer and "while" until the next one is different:
var i, j, n = 10;
setInterval(function () {
while ((j = Math.floor(Math.random() * n)) === i);
document.write(j + ' ');
i = j;
}, 1000);
Or even simpler, just add 1... :-D Modulo n to prevent index overflow:
var i, j, n = 10;
setInterval(function () {
j = Math.floor(Math.random() * n);
if (j === i) j = (j + 1) % n;
document.write(j + ' ');
i = j;
}, 1000);
First solution applied to your code:
var whatAmI = ["Webdesigner", "Drummer", "Techie", "Linguistics student", "Photographer", "Geek", "Coder", "Belgian", "Batman", "Musician", "StackExchanger", "AI student"];
var j;
var i = 1; // since you start with "Drummer"
var n = whatAmI.length;
function changeSubTitle() {
setTimeout(function () {
while ((j = Math.floor(Math.random() * n)) === i);
$(".page-header > h2").animate({
"opacity": 0
}, 700, function () {
$(this).text(whatAmI[j]);
$(this).animate({
"opacity": 1
}, 700, changeSubTitle);
});
}, 1000);
i = j;
}
changeSubTitle();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<header class="page-header">
<h1>Bananas</h1>
<h2>Drummer</h2>
</header>
To give an even chance of all elements except the previous one being used do the following:
var i, j, n = 10;
setInterval(function () {
// Subtract 1 from n since we are actually selecting from a smaller set
j = Math.floor(Math.random() * (n-1));
// if the number we just generated is equal to or greater than the previous
// number then add one to move up past it
if (j >= i) j += 1;
document.write(j + ' ');
i = j;
}, 1000);
The comments in the code should explain how this works. The key thing to remember is that you are actually selecting from 9 possible values, not from 10.
You should initialize i to be a random element in the array before starting.
For a simple walk through on a 3 element array with the second element selected:
i=1, n=3
The random result gives us either 0 or 1.
If it is 0 then j >= i returns false and we select element zero
If it is 1 then j >= i returns true and we select the third element.
You can do the same walk through with i being 0 and and i being 2 to see that it never overruns the buffer and always has an equal chance to select all other elements.
You can then extend that same logic to an array of any size. It works exactly the same way.
var whatAmI = ["Webdesigner", "Drummer", "Techie", "Linguistics student", "Photographer", "Geek", "Coder", "Belgian", "Batman", "Musician", "StackExchanger", "AI student"];
var prev = 1; //1 because "drummer" is the first array element displayed in the HTML
function getIndex(){
var next = Math.floor(Math.random() * whatAmI.length);
if(next==prev)
return getIndex();
else {
prev = next;
return next;
}
}
function changeSubTitle() {
setTimeout(function () {
$(".page-header > h2").animate({
"opacity": 0
}, 700, function () {
$(this).text(whatAmI[getIndex()]);
$(this).animate({
"opacity": 1
}, 700, changeSubTitle);
});
}, 1000);
}
changeSubTitle();
Try this method.

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

setTimeout passing arguments issue

I'm trying to pass an index of element and slideUp each list item content with delay
here is my code
for(var i = 1; i <= $("#colContainer li").length ; i++) {
var el = $("#colContainer li:nth-child(" + i + ") .colContent");
var delay = function() {
slide(el);
};
setTimeout(delay, 10);
function slide(el){
el.slideUp();
};
};
but every time just the last one slides up
what I expect is they slideUp from index 1 to the end with delay
I also tried this
index = $(this).parent("li").index();
for(var i = 1; i <= $("#colContainer li").length ; i++) {
(function(i) {
var el = $("#colContainer li:nth-child(" + i + ") .colContent");
var delay = function() {
slide(el);
};
setTimeout(delay, 10);
function slide(el){
el.slideUp();
};
})(i);
};
but they all slide at once, i want index 1 slide, after that index 2 and ...
IS THERE ANY WAY WITH FOR LOOP ??
This is because var el is scoped to the function block, not the loop block.
Try something like this:
for( var i=1; ......) { (function(i) {
var el = ...
// rest of your code, unchanged
})(i); }
You need a closure to scope the value of el for each iteration of the loop.
for(var i = 1; i <= $("#colContainer li").length ; i++) {
var el = $("#colContainer li:nth-child(" + i + ") .colContent");
(function(el) {
setTimeout(function(){
el.slideUp();
},10);
})(el);
}
However this will still cause them to all animate at the same time which if that is the desired result, you could just do it all in one step with jQuery.
If you want them to animate one at a time you can do this:
for(var i = 1; i <= $("#colContainer li").length ; i++) {
(function(i) {
var el = $("#colContainer li:nth-child(" + i + ") .colContent");
setTimeout(function(){
el.slideUp();
}, i * 10);
})(i);
}
Did you want them to be queued or for a 10 millisecond delay before they all slide up?
Do you require the for loop?
Wouldn't the following do the latter?
setTimeout(function() {
$("#colContainer li .colContent").slideUp();
}, 10);
Queued slide example:
(function slideContent(index) {
$("#colContainer li:nth-child(" + index + ") .colContent").slideUp();
if ($("#colContainer li:nth-child(" + (index + 1) + ") .colContent").length == 1) {
setTimeout(function() { slideContent(index + 1); }, 250);
}
})(1);
Unless your intention is to have them all animate at the same time, you can't set them up in a loop this way. If you do, they're all executed (almost) simultaneously and as you say, you'll only actually see the last one.
You need to trigger each successive one from the completion of the previous one. Chain them together with callbacks.
delay should set up the next setTimeout. Then you'll get the result you're after.
EDIT
Given the other answers here, I'll add that you'll probably want to increase your pause time from 10ms to something like 100 and then use the *i solution that the others have suggested. Multiplying 10ms by i isn't going to get you a whole lot in the way of noticeable delay. I'd start with 100ms and if that's too jerky move down from there in increments of 10ms till you have an animation that makes you happy.

setInterval on a for loop?

for (var i=list.length; i>=0; i--){
//do something....
}
I want to use a setInterval to make this process take 1 whole min no matter how many items are in the list. So if there are 10 items it would trigger every 6sec, 30items, every two seconds, etc.
Thanks for any help!
Something like this could be done:
var list = [1,2,3,4,5,6,7,8,9,10];
var timeFrame = 60000;
var interval = timeFrame / (list.length-1);
var i = 0;
(function iterate () {
if (list.length > i) {
console.log(list[i]);
i++;
}
setTimeout(iterate, interval);
})();
JsFiddle Demo
I am not sure if this is what you're looking for, but this will "iterate" through all items in the list, without using for loop, in the given timeframe. The function will always "call itself" using setTimeout. The timeout is calculated in the beginning based on the number of items.
This solution is much more "trustable" than setInterval. The next timeout will be set when the previous action is already done, so it won't stack up.
var totalItem = list.length;
setInterval(function(){ alert('');}, 60000/totalItem);
You'd do
function code(i) {
return function() { alert(i); };
}
var period = 60 * 1000 / (list.length - 1);
for (var i=list.length; i>=1; i--){
setTimeout(code(list[i - 1]), period * (i - 1));
}
Try something like the following:
var interval = 2;
for (var i=list.length; i>=0; i--){
setTimeout(your_code_here(), i*interval);
}

Categories