jQuery: Problem assigning setIntervals to an array - javascript

I'm attempting to run multiple animations (slideshows of sorts) on one page, but the code is only working for one of the (in my case) 3 slideshows that are actually present.
The issue is not with the animation but with the actual initialisation and running of functions (explained better below by looking at the code):
The HTML:
<div class="someclass1" rel="slideshow" type="fade" duration=8500>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
<div class="someclass2" rel="slideshow" type="slide" duration=4000>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
<div class="someclass3" rel="slideshow" type="fade" duration=5000>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
jQuery:
$(function() {
var plays = [];
var duration = 0;
var targets = [];
var t = "";
var $obs = $('div[rel="slideshow"]')
for(var x = 0; x < $obs.length; x++){
$obs.eq(x).children('.wrapper').eq(0).addClass('active');
$obs.eq(x).children('.wrapper').css({opacity: 0.0});
$obs.eq(x).children('.active').css({opacity: 1.0});
$obs.eq(x).children('.navigation a.slide-buttons').eq(0).addClass('current');
// Set duration
duration = $obs.eq(x).attr('duration');
// Set target
targets = $obs.eq(x).attr('class').split(' ');
t = '';
for(var i=0; i<targets.length; i++){
t += '.' + targets[i];
}
if($obs.eq(x).attr('type')==='fade'){
plays[x] = setInterval(function(){fadeSwitch(t);}, duration);
}
else if($obs.eq(x).attr('type')==='slide'){
plays[x] = setInterval(function(){slideSwitch(t);}, duration);
}
}
});
Through testing, I have shown that the loop runs successfully and passes the appropriate target and duration to either fadeSwitch or slideSwitch for all 3 runs of the loop.
fadeSwitch and slideSwitch are identical except for the animation part, for example:
function fadeSwitch(target) {
var $active = $(target+' .active');
if ( $active.length === 0 ){ $active = $(target+' .wrapper:first');}
var $next = $active.next('.wrapper').length ? $active.next('.wrapper')
: $(target+' .wrapper:first');
// FADE ANIMATIONS
$active.animate({opacity : 0.0}, 500, function() {
$active.addClass('last-active');
});
$next.animate({opacity: 1.0}, 500, function() {
$active.removeClass('active last-active');
$next.addClass('active');
});
}
However this function will run only using the last found target (i.e t = '.someClass3'). Even though by placing console.log alerts in the setInterval functions I know that it is applying the correct variables.
e.g.
plays[0] = setInterval(function(){fadeSwitch('.someclass1');}, 8500);
plays[1] = setInterval(function(){fadeSwitch('.someclass2');}, 4000);
plays[2] = setInterval(function(){fadeSwitch('.someclass3');}, 5000);
Yet as I have tried to (badly) explain, if I place a console.log inside of fadeSwitch to test what is being passed as the target when it runs (remember it is set to run after an interval, so by the time the .someClass1 function runs for the first time, the plays[] array is full and finished) the log shows that the target is always .someClass3 and it never succesfully runs for anything else but that last entered target.
Any suggestions or help is greatly appreciated.
Thank you.

The value of t is being "closed over" by your anonymous functions when you call setInterval. For every iteration of your loop you create a new anonymous function, and like you said, at the time t has the right value.
The problem is that by the time each function executes t's value has changed (it will hold the last value of the loops), and all three anonymous functions refer to the same t variable (that is the nature of a closure and the lexical scoping of javascript). The quick fix is to give each anonymous function the right value and not a reference to t:
Change this:
plays[x] = setInterval(function(){fadeSwitch(t);}, duration);
to this:
plays[x] = setInterval((function(t2){ return function(){ fadeSwitch(t2); }; })(t), duration);
And obviously the same for the same line with slideSwitch.
Another thing I felt I should point out: You're using invalid attributes in your html, consider finding an alternative, like hidden embedded markup (e.g. <div class="duration" style="display:none">5000</div>), or class names, or html5 data attributes, instead of <div duration=5000>

Related

Building a JS automated slideshow, but loop and/or setInterval instances run concurrently?

I am attempting to build a very simple automated slideshow from scratch, but I'm coming across some difficulty. I've built working slideshows before, but not ones that were automated. So I began building one and tried using a for loop structure or the setInterval() method to mimic a loop:
$(function carousel() {
$('.slide:not(:first-child)').hide();
var slide1 = $('.slide:first-child');
var slide2 = $('.slide:nth-child(2)');
var slide3 = $('.slide:nth-child(3)');
var slide4 = $('.slide:last-child');
function moveSlide(currentSlide, nextSlide) {
setInterval(function () {
currentSlide.hide("slide", {direction: "left"}, 1000);
setTimeout(function () {
nextSlide.show("slide", {direction: "right"}, 1000);
}, 1000);
}, 1500);
}
var arr = [moveSlide(slide1, slide2), moveSlide(slide2, slide3), moveSlide(slide3, slide4)];
var i = 0;
setInterval(function () {
if (i < arr.length) {
arr[i] += 1;
console.log(i + "=>" + arr[i]);
} else {
return;
}
i++;
}, 1500);
});
Here's a Codepen.
Unfortunately, this did not go well, and I know why. I understand that in JS, the code will continue to execute and will not wait for the information in a loop to finish if using setInterval or setTimeout. So my question is, what would be a good workaround that doesn't require using an external library or plugin? If you could try to stick as close to my source code as possible that would be awesome. Thanks!
There are a few problems with your code. Calling moveSlide() will hide the specified slide and (after the timeout) show the specified next slide, but your use of setInterval() within that function means it will keep trying to hide the same first slide and then show the next one.
The line with var arr = [moveSlide(slide1, slide2),... is calling the moveSlide() function immediately and putting its return values into the array. So that means you've got several intervals all running (one per call to moveSlide()), and all stepping over each other trying to hide and show the same elements. Also the return value is undefined, so basically you've got an array full of undefined.
What I would suggest you do instead is something like the following:
$(function carousel() {
// get a list of *all* slides:
var slides = $('.slide');
// hide all but the first:
slides.slice(1).hide();
var current = 0;
setInterval(function() {
// hide the current slide:
slides.eq(current).hide(1000);
// increment the counter, wrapping around from end of the
// list to the beginning as required:
current = (current + 1) % slides.length;
// show the next slide after a timeout:
setTimeout(function () {
// note that `current` was incremented already:
slides.eq(current).show(1000);
}, 1000);
}, 3500); // make the interval larger than the hide/show cycle
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="slide">Slide 1</div>
<div class="slide">Slide 2</div>
<div class="slide">Slide 3</div>
<div class="slide">Slide 4</div>
<div class="slide">Slide 5</div>
Notice that I don't need individual variables for the individual slides, I just have a single slides variable that is a jQuery object containing all of the slides. This means you can easily change the number of slides on the page without changing the JS at all.
Note that I was too impatient to get jQueryUI to work in the snippet so I've just used a simple .hide() and .show(), but obviously that isn't the important part of the code I've shown.

Converting a JavaScript Countdown to a jQuery Countdown and adding an Interval

I found this JS-Countdown Script at JSFiddle.
EDIT:
I'm using the code of rafaelcastrocouto now, which is nearly perfect. I wanted a 10-seconds JQuery Countdown-Script with an interval that resets the countdown timer at 5 seconds and starts over again and again, but only for a specific class with a specific id on the whole HTML page. If it drops to 0, the countdown should stop. Also I want to reset specific counters to 10.
It's about a WebSocket that refreshes every second and depending on the data I get for specific counters I want to reset them or they should count down to zero.
New JSFiddle: http://jsfiddle.net/alexiovay/azkdry0w/4/
This is how I solved with jquery and native setInterval...
var setup = function(){
$('.count').each(eachSetup);
};
var eachSetup = function(){
var count = $(this);
var sec = count.data('seconds') ;
count.data('count', sec);
};
var everySecond = function(){
$('.count').each(eachCount);
};
var eachCount = function(){
var count = $(this);
var s = count.data('count');
count.text(s);
s--;
if(s < 0) {
s = count.data('seconds');
}
count.data('count', s);
};
setup();
setInterval(everySecond, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p class="count" data-seconds="5"></p>
<p class="count" data-seconds="10"></p>
<p class="count" data-seconds="15"></p>
You have sever errors in code, e.g.
setTimeout(cd_go(id), 1000); - should point to function reference not to function execution. setTimeout also returns the timeout id. You must past that id to clearTimeout
clearTimeout(this); it should take id instead of global object (window) or undefined if you are working in strict mode
loop = setInterval(function(id) { … } - id points to undefinded as you are not passing any value for it

Soundcloud widget & for loop

i created music blog using soundcloud widgets. I trigger a button "Play all", so when you hit it, it plays all widgets.
$(function(){
var playAll = $('.playAll');
var widget0 = SC.Widget(playAll.find('iframe')[0]);
var widget1 = SC.Widget(playAll.find('iframe')[1]);
var widget2 = SC.Widget(playAll.find('iframe')[2]);
widget0.bind(SC.Widget.Events.FINISH,function() {
widget1.play();
widget0.unbind(SC.Widget.Events.FINISH);
});
widget1.bind(SC.Widget.Events.FINISH,function() {
widget2.play();
widget1.unbind(SC.Widget.Events.FINISH);
});
$("#playSound").click(function() {
widget0.toggle();
});
});
It works, but what i'm trying to do is "for" loop, but because lack of js / jquery skills, it doesn't work.
I love the enthusiasm to refactor code and the desire to use a higher level pattern, looping, instead of hard coding like you have. That being said the "middle space" problem is one of the harder problems to solve when iterating over a list.
You have to decide on a couple of things:
You have a task your going to perform n-1 times so you have to decide if your going to skip the first element or the last.
What task your going to perform in each iteration.
What you'll do after the loop finishes.
If you'll use a functional solution or a classic "for" loop.
Lets start with the task. We'll take the task from your example and put it in a function.
function connectFrames(now, next) {
now.bind(SC.Widget.Events.FINISH, function() {
next.play();
now.unbind(SC.Widget.Events.FINISH);
});
}
With this function we can use the index from our loop to pass in the current and next elements. The only exception will be when we are on the last element when there will be no "next", so that is the one we will skip. We will use the jQuery each for a modern / functional solution.
//One query to select all iframes inside .playAll
var playAll = $('.playAll iframe');
//The jQuery "loop" http://api.jquery.com/each/
playAll.each(function(index) {
var now = playAll[index],
next = playAll[index + 1];
//If their is no next... skip.
if(!next) return;
//Use the frames we have in our function
connectFrames(now, next);
});
The last thing we have to do is write in our final step which would be to start playing the content and we are done. Here is the complete code:
function connectFrames(now, next) {
now.bind(SC.Widget.Events.FINISH, function() {
next.play();
now.unbind(SC.Widget.Events.FINISH);
});
}
//One query to select all iframes inside .playAll
var playAll = $('.playAll iframe');
//The jQuery "loop" http://api.jquery.com/each/
playAll.each(function(index) {
var now = playAll[index],
next = playAll[index + 1];
//If their is no next... skip.
if(!next) return;
//Use the frames we have in our function
connectFrames(now, next);
});
//initiator from example
$("#playSound").click(function() {
playAll[0].toggle();
});
Assuming the HTML looks somewhat like this :
<div class="playAll">
<div>
<iframe></iframe>
<iframe></iframe>
<iframe></iframe>
</div>
</div>
You get get the list of all iframes inside the div with class "playAll" (no matter the depth), and iterate:
var iframes = $(".playAll iframe");
for (i = 0; i < iframes.length; i++) {
// do something with iframes[i]
}
http://api.jquery.com/descendant-selector/

create simple rotation with pause

How can I cycle through a series of elements, adding a class, pausing then removing the class and moving on to the next element. I have tried setInterval and I have tried setTimeout, but cannot seem to get it to work.
My Javascript
var numpromos = $('.promoteBlock').length;
var promonum = 1;
while (numpromos > promonum){
setInterval(function() {
$('#promoteCont .promoteBlock').fadeOut().removeClass('active');
$('#promoteCont #promo'+promonum).addClass('active');
}
}, 3000);
promonum++;
}
My HTML
<div id="promoteCont">
<div id="promo1" class="promoteBlock">Promotion No.1</div>
<div id="promo2" class="promoteBlock">Second Promo</div>
<div id="promo3" class="promoteBlock">Another one</div>
</div>
function playNext(){
console.log("playNext");
var active = $('#promoteCont').find(".promoteBlock.active");
if( active.length == 0 )
active = $(".promoteBlock:eq(0)");
var fout = active.next();
if( fout.length == 0 )
fout = $(".promoteBlock:eq(0)");
active.fadeOut().removeClass('active');
fout.fadeIn().addClass('active');
setTimeout(function(){
playNext();
},3000);
}
setTimeout(function(){
playNext();
},3000);
http://jsfiddle.net/p1c3kzj7/
Take things out of the while loop. You only need to set the interval once. Perform your state calculation (which item is selected) within the callback method itself. See below, which I believe is what your looking for.
// Global variables to maintain state... I'm sure I'll get some comments about these :p
var numpromos = $('.promoteBlock').length;
var promonum = 1;
$document.ready(function()
{
setInterval(function() {
$('#promoteCont .promoteBlock').fadeOut().removeClass('active');
$('#promoteCont #promo'+promonum).addClass('active');
promonum++;
if(promonums > numpromos)
promonum = 1;
}, 3000);
});

Help with JQuery Callback

As per my previous question, I have a working animation which fades in and out each element within the div slideshow. The problem is that I want this animation to continue from the beginning once it has reached the last element. I figured that was easy and that I'd just place an infinite loop inside my JQuery function, but for some reason if I insert an infinite loop, no animation displays and the page hangs. I also cannot find anything in the documentation about how properly place a callback. How can I get this code to restart from the beginning of the animation once it finishes iterating over each object and why is an infinite loop not the right way to go about this?
<div id="slideshow">
<p>Text1</p>
<p>Text2</p>
<p>Test3</p>
<p>Text4</p>
</div>
<script>
$(document).ready(function() {
var delay = 0;
$('#slideshow p').each(
function (index, item)
{
$(this).delay(delay).fadeIn('slow').delay(800).fadeOut('slow');
delay += 2200;
}
);
});
</script>
You could do something like this:
$(function() {
var d = 2200;
function loopMe() {
var c = $('#slideshow p').each(function (i) {
$(this).delay(d*i).fadeIn('slow').delay(800).fadeOut('slow');
}).length;
setTimeout(loopMe, c * d);
}
loopMe();
});
You can give it a try here.
Instead of keeping up with a delay, you can just multiple it by the current index in the loop...since the first index is 0, the first one won't be delayed at all, then 2200ms times the amount of elements later, do the loop again. In the above code d is the delay, so it's easily adjustable, and c is the count of elements.
This solution is in my opinion more elegant, also more natural, it is easier to control, to correctly edit values of delays etc. I hope you'll like it.
$(document).ready(function () {
var elementsList = $('#slideshow p');
function animationFactory(i) {
var element = $(elementsList[i % elementsList.length]);
return function () {
element.delay(200).fadeIn('slow').delay(800).fadeOut('slow', animationFactory(i + 1));
};
}
animationFactory(0)();
});

Categories

Resources