Iterate through an array of DIVs and fadeIn/fadeOut - javascript

I want to build a simple banner image slider functionality, but instead of sliding, the images are supposed to fade in and out.
It's a Drupal project, so I don't know how many images are going to be used.
The structure is simple enough:
<div class="banner-container">
<!-- potentially a lot of divs with background images -->
</div>
What I want to do is essentially iterate through this "array" of divs let the currently active one fade out and let the one that the array is pointing to at the moment fade in.
I thought I could get it to work like this:
var a = $('#banner-container').children();
while(1) {
a.each(function(){
$('.active').fadeOut(500).removeClass('active');
$(this).fadeIn(500).addClass('.active');
})
}
Obviously there is a lot wrong in my code, I have run into multiple issues, but the one I cannot seem to solve is getting the loop to wait between the iterations. To my knowledge, a traditional loop like foreach could just have some sort of wait function added to the end of it and execute that wait in every loop. But the .each() seems to execute every iteration simultaneously. How can I loop through an array like this (potentially infinitely) and wait like 5 seconds every time?
I read somewhere that the animate functions work asynchronously, and I assume the same goes for CSS transforms, so creating fadeIn, and fadeOut classes and try this with CSS wouldn't work.

You're looking for the setTimeout and setInterval functions: You pass a function into them with a timeout (in milliseconds), and they call the function after that many milliseconds (repeating, in the case of setInterval).
So for instance:
var a = $('#banner-container').children();
var index = 0;
run();
function run() {
a.filter('.active').fadeOut(500).removeClass('active');
a.eq(index).fadeIn(500).addClass('active');
index = (index + 1) % a.length; // Wraps around if it hits the end
setTimeout(run, 1000);
}
That will update the first div, then a second later the next div, and so on.
Live Example:
// Scoping function to avoid creating globals
(function() {
var a = $('#banner-container').children();
var index = 0;
run()
function run() {
a.filter('.active').fadeOut(500).removeClass('active');
a.eq(index).fadeIn(500).addClass('active');
index = (index + 1) % a.length; // Wraps around if it hits the end
setTimeout(run, 1000);
}
})();
#banner-container a {
display: none;
position: absolute;
}
#banner-container {
position: relative;
}
<div id="banner-container">
<a>one</a>
<a>two</a>
<a>three</a>
<a>four</a>
<a>five</a>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
In the above, I've used setTimeout, which only schedules a single callback, because I find I write fewer bugs when the thing I'm calling has to explicitly re-schedule the next call. :-) But you could also use setInterval(run, 1000); which would call run at roughly one-second intervals, until/unless you told it to stop via clearInterval.

you can't use a loop for this because of the nature of javascript, you will have to use recursion instead, something like this untested code should work..
function loopNext(currentIndex){
// set the current index on the first call
if('undefined' === typeof currentIndex) currentIndex=0;
// the group of image divs
var images = $(".imagesDivs");
// if the current index doesn't exist,
// we've looped thru them all, reset the index
if('undefined' === typeof images.eq(currentIndex)) currentIndex=0;
// fade out the previous one and fade in the first one
images.fadeOut('slow', function(){
images.eq(currentIndex).fadeIn('slow');
});
// fade the next one every 3 seconds or so
setTimeout(function(){
loopNext(currentIndex+1);
}, 3000);
}
To start the loop you can just call it without any parameters..
loopNext();

you can use setTimout and go to the next element or start over:
setTimout(function(){
var current = $('.active');
var next = current.next().length > 0 ? current.next() : $('.banner-container > div').first();
current.fadeOut(500).removeClass('active');
next.fadeIn(500).addClass('.active');
},5000);

Related

For Loop Iteration Happening all at Once Rather than By Delay

For one of my elements on my page, I want the text to change every ten seconds, and for the class to be changed. Text changing is easy, as is class changing, but I'm having trouble with my for loop, and I feel like I'm missing something.
I want to have the for loop choose a random faction in an array, and then apply that to the element. For my testing, I've been using console.log rather than DOM manipulation.
First, I set up my array:
var factions = ["Enforcers", "Reapers", "Ular Boys", "Roaches"];
Then, I want a variable that is a number chosen at random in reference to this array:
var x = factions[Math.floor(Math.random()*factions.length)];
From that, I want the ability to run the Math.floor and Math.random functions elsewhere.
function reDefine() {
x = factions[Math.floor(Math.random()*factions.length)];
console.log(x);
}
Finally, I want the for loop to run 200 times (I've chosen 200 times because it's far and beyond the time the user will be staying on the site), so I told it to count to 200 (i = 0; i < 200). After that, I wanted each time it iterated, to wait 10s, so I have a Timeout function with a delay of 10000 (milliseconds). Then, the code to reDefine and then, in the case of testing, console.log the new definition of the x variable.
function reChange() {
for (var i = 0; i < 200; i++) {
setTimeout(function() {
reDefine();
console.log("Chosen faction is now: " + x);
}, 10000);
}
}
Instead of counting to 1 (the first iteration), waiting 10000, and then redefining x, it redefines x two hundred times, then logs them all.
Is there something I'm specifically doing wrong here, perhaps with the Timeout function?
Is there something I'm specifically doing wrong here, perhaps with the Timeout function?
Yes! You're scheduling a bunch of deferred callbacks, but not actually waiting until one has finished to schedule the next.
You can fix that with something as simple as:
function reChange(currentIndex) {
setTimeout(function() {
reDefine();
console.log("Chosen faction is now: " + factions[currentIndex]);
// If we haven't gotten to the end of the list, queue up another one
var nextIndex = ++currentIndex;
if (nextIndex < factions.length) {
// Enqueue the next faction
reChange(nextIndex);
}
}, 10000);
}
Make sure to note that the function without the timeout has closure over the value of currentIndex for each call of reChange. That is, the next invocation does not replace currentIndex in any previous timeout, since primitives (including numbers) are passed by value. Closure in JS can be a tricky thing.
The core problem is that your execution right now looks like:
for each item
wait
log
rather than:
for the current item
wait
log
repeat
Because JS is single-threaded (for most intents and purposes), setTimeout adds a callback to be executed later. It doesn't block until the timeout has expired, like a traditional sleep would do.

Javascript - passing index to setInterval callback

The problem
I'm trying to simplify a long javascript code and i have a problem with identifying callbacks.
I have a large array with elements to animate on page
[selector, activate interval, hide after]:
things_to_move = [
['.ufo, .chefoven, .sushi', 9900, 2000],
['.hotdog,.pizzaman,.boyballon', 12090, 3600],
(...)
]
Basically, the aim is to to activate each of the selectors every x seconds, and hide them x seconds later, as per the example above.
Current code
After many tries, I ended up with this:
// Activate the element, and set timeout to hide it
var showFun = function(index1) {
$(things_to_move[index1][0]).addClass('move');
setTimeout( function(){hideFun(index1)},things_to_move[index1][2]);
}
// Hide the element
var hideFun = function(index2) {
$(things_to_move[index2][0]).removeClass('move');
}
// Loop through all items and set the interval for each one
for(_A=0; _A < things_to_move.length; _A++) {
setInterval(function(){showFun(_A)}, things_to_move[_A][1]);
}
But of course this doesn't work. Every time the showFun function is called, it takes the value of _A after the loop finished and not the value at which setInterval was set.
Question
So the question is, how can i pass a unique index into the setInterval callback, so the callback knows which array item to use?
Final solution
If anyone is interested, the final solution: Fiddle
The most direct way to solve it is using closures.
Try something like this:
for(_A=0; _A < things_to_move.length; _A++) {
setInterval((function(_innerA){
return function(){ showFun(_innerA); };
})(_A), things_to_move[_A][1]);
}

slideshow counter disappears

I have this code that is supposed to generate a counter for a slideshow and then change the picture and the corresponding number color in the counter. However, after the slideshow cycles through twice, the counter changes to display:none and then reappears and disappears every time the slideshow begins its cycle.
//icons for newsreel guide
for(i=0;i<document.getElementsByClassName("news").length;i++){
var count=i+1;
$('#counter').append('<span class="count">'+count+'</span>');
}
//newsreel script
$(".news").hide();
setTimeout (function() {
var wait = $(".news:last").index()*12000+12000;
function newsreel(){
var i=0;
(function showNews(elem){
if(i==document.getElementsByClassName("count").length){
i=0;
}
document.getElementsByClassName("count")[i].style.color="#000";
elem.fadeIn(2000,function(){
elem.delay(8000).fadeOut(2000,function(){
document.getElementsByClassName("count")[i].style.color="#3159a0";
i=i+1;
$(this).next().length && showNews($(this).next());
});
});
})
( $(".news:first"));
setTimeout (arguments.callee, wait);
}/*end newsreel()*/
newsreel();
}, 2000);
At first I thought it was using the deprecated arguments.callee but I changed that and it still happens on cue. Any ideas?
I checked your code, and the problem is in this line :
$(this).next().length && showNews($(this).next())
.next() is getting the next sibling. Your counter is a sibling of .news. To solve your problem, do this:
$(this).next().length && showNews($(this).next('.news'))
That will select the next sibling with the class news.
I suspect it's because your showNews function is never running. I think the JavaScript engine is evaluating
(function showNews(elem){
//...
})
and
( $(".news:first"));
as two different expressions, rather than passing $(".news:first") as a parameter to showNews as you intend. Since ; at the end of a line is optional in JS, the parser will insert one automatically if the result is valid JavaScript. In this case, it defines a function but never calls it, then builds a jQuery sequence but never uses it.
Try removing the carriage return between the two:
(function showNews(elem){
//...
})($(".news:first"));

Making a total number count appear to increase

Basically I have a class counting system that displays the number of classes and displays them in a span element. Below is the code:
$.get('other.html', function(data) {
$('#total').html($('.doc', data).length);
});
This works perfectly, however I'd like a way to have the numbers increasing one by one since the span element contains 0 when the page loads. Here's an example (the numbers increasing on here).
For this I have tried setTimeout and despite this not working anyway, I released it would simply delay the function and then display the end number. I have heard of periodical or something similar being used but could not find this in the example source code.
I am really sorry for more poor phrasing. If you have no idea what I mean then just ask and I'll try rephrase or find a better example.
The key is the function which increases the number should set a setTimeout to call itself, before termination. This way it will always be called again. If you want the option to stop the incrementing, you can add a global variable, and the function will only set a new timeout when that variable is true.
Example:
var doIncrement = true;
var numberToIncrement = 0;
function increment {
numberToIncrement++;
$('#mySpan').text(numberToIncrement);
if (doIncrement) {
setTimeout(increment, 1000);
}
}
You could use the setInterval function that allows you to run code at some time intervals. clearInterval allows to stop the task.
http://jsfiddle.net/d52Pw/
var $totalEl = $('#total');
$.get('other.html', function(data) {
var len = $('.doc', data).length,
count = 0,
int = setInterval(function () {
if (++count === len) {
//when we reach len, we stop the task
clearInterval(int);
}
$totalEl.html(count);
}, 500); //run every 1/2 sec
});

How to stop long javascript loops from crashing the browser?

I have a button in my page that's looping a series of DIVs and editing them (appending text mostly, nothing serious),
The thing is, the number of DIVs is changing by the user (The user can add or remove them freely),
I'm looping the DIVs via jQuery $.each function:
var DomToEdit = $('.divs_to_edit');
$.each(DomToEdit, function() { $(this).append('text'); ... });
the variable DomToEdit contains somewhat unlimited number of divs, and then I refer to them via the $.each function.
Sometimes while doing the $.each loop the user gets to wait for a couple of secons, and in worse cases the browser is crashing
Is there a way to prevent this? Maybe having the loop "sleep" after 50 DIVs?
Thanks
EDIT: I didn't use the same ID, sorry - it was a flaw in my explanation. I use the same class. :)
The first argument of the function in the .each handler is the index of the current element. you can simply add a check before it, and return false to stop the loop.
$.each(DomToEdit, function(i) { // or DomToEdit.each(function() {
if (i === 50) return false;
..
DomToEdit is a jQuery object, so $.each(DomToEdit, fn) and DomToEdit.each(fn) are equivalent.
A more effective method is to cut off the elements, using .slice(0, 50).
DomToEdit.slice(0, 50).each( function(i) {
..
Add a timer which will execute append 50 div's every 5 seconds and works thru the array of div until it finishes iterating all div.
Below code works on 50 div every 5 seconds.
var DomToEdit = $('#divs_to_edit');
var timer = setInterval( function () { //<-- Create a Timer
$.each(DomToEdit, function(index) { //<-- Iterate thru divs
if (index == 50) return; //<-- Return on 50 for later
$(this).append('text');
});
DomToEdit = DomToEdit.slice(0, 50); //<-- Slice the processed div's
// Clear timer when all elements are processed.
if (DomToEdit.length == 0) {
clearInterval(timer);
}
}, 5000); // <-- Do the steps on every 5 secs
Whenever I think that code can potentially cause a crash, I'll create a self-decementing breaker variable that breaks out of a loop after a certain number of loop cycles.
var breaker = 100;
while(true) {
breaker--;if(breaker<0){console.log("Oh snap batman");break;}
console.log("CRASH");
}
The method could execute alternative code that works around the crash as well. Usually, I just try to fix the code somehow ;)
You could setTimeout to 0 in order to queue the processing of each element into the execution stack (0 makes it just queue without delay):
$.each(DomToEdit, function() {
var elem = $(this);
setTimeout(function() { elem.append('text'); }, 0);
});
You could queue the tasks and then execute tasks
in batches of X every Y milliseconds:
var queue = [];
$.each(DomToEdit, function () {
queue.push( $.proxy( function () {
$(this).append('text');
}, this ));
});
window.setInterval( function(){
var l = 100;
while( queue.length && l-- ) { //Keep executing tasks until there
//is no more or maximum amount of tasks
//executed for this batch is executed
queue.shift()();
}
}, 50 );
The real fix is of course carefully review what you are doing and fix that. $('#divs_to_edit') always returns a single element max so .each doesn't make much sense here for example...
It's possible with an extremely large number of elements, that it would actually be less processor intensive to pull the entire container element as a string and run a Javascript .replace() on it and replace the entire container, than looping through hundreds of thousands of elements?

Categories

Resources