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"));
Related
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);
On this page: http://koncordia.marketingassociates.com/19892arc/
I have a slideshow that I created custom prev/next links for. Each selection you make on the page advances it one slide forward. The progress bar at the top allows you to click a previous slide, and jump more than one back if you want (you can go from step 4 or step 1 for example).
This multi-step jump works fine in all the current major browsers, but the client uses IE9, and this is where I do not understand the source of the issue.
The following are the relevant methods in this issue. To mimic a user jumping back one or more slides I have a for loop iterate over simulatePrevClick() as many times as necessary; it's not sexy but it works.
The issue arises on the initial pass in IE9. The console spits out "undefined" for the first pass, but it says 0 for all other browsers (including IE 10 and 11) which is correct. If I remove the method call within the loop the iteration works perfectly, so it has something to do with the .click() event or way the method is called, but I don't know what.
No matter what, IE9 will show the immediate previous slide no matter how many they click back; the progress bar be out of sync if they click back more than one in this instance. The undefined result is not showing as an error, either.
//Highlight the right number of progress buttons
highlightProgressBar: function( slideNumber ) {
$(".btn-progress").attr('disabled', 'disabled').removeClass('active'); //Disabled all
$("#progress-wrapper a:lt(" + slideNumber + ")").removeAttr('disabled'); //Disable select number
$("#progress-wrapper a:eq(" + (slideNumber - 1) + ")").addClass('active'); //Add active to the specified button clicked
},
simulateNextClick: function () {
//The value of this must match what the responsiveslides function creates for the prev/next buttons (seen when you inspect element)
$(".transparent-btns_nav.transparent-btns1_nav.next").click();
},
simulatePrevClick: function () {
//The value of this must match what the responsiveslides function creates for the prev/next buttons (seen when you inspect element)
$(".transparent-btns_nav.transparent-btns1_nav.prev").click();
},
toggleProgressBar: function( clickedSlideNumber, activeSlideNumber ) {
var numSlides = activeSlideNumber - clickedSlideNumber;
for (var i=0; i < numSlides; i++) { //Anticipate user may click more than one step back
this.simulatePrevClick();
console.log(i); // **shows "undefined" on first pass in IE9 only**
}
this.highlightProgressBar(clickedSlideNumber);
}
Try to move the var i = 0 declaration out of the loop.
var i = 0;
for (; i < numSlides; i++) {}
It's really strange that that should happen.
This is just a guess, but I looked through the rest of your source code, and its possible that the root of your problem could be due to whenever you actually implement your toggleProgressBar function, in this area:
$(".btn-progress").click(function() {
var currentSlideID = $("#progress-wrapper").find('a.active').attr('id').split("-");
var clickedSlideID = $(this).attr('id').split("-");
slideFn.toggleProgressBar( clickedSlideID[1], currentSlideID[1] );
});
If I see right, your toggleProgressBar wants to accepts two numbers. However, what you're passing in are string literals:
slideFn.toggleProgressBar( "2", "1" );
ID attributes are output as strings, not numbers. I just tested the following in Chrome, and it worked:
"2" - "1" === 1 //true
This is because I guess V8 (Chrome's JS engine) coerces the two string literals into numbers. However, (while I have not tested it), this tells me that it's possible that IE might not be coercing the two strings into numbers (like I said, I don't know this for a fact, but this is something you might try debugging). Try this and see if it has any effect:
//first option
slideFn.toggleProgressBar( +clickedSlideID[1], +currentSlideID[1] );
//the + sign will typecast your strings into numbers
//second option
slideFn.toggleProgressBar( parseInt(clickedSlideID[1]), parseInt(currentSlideID[1]) );
However, in my experience, parseInt runs a little bit slower than using + to typecast the strings into numbers.
IE uses the Chakra JS engine, which I believe follows the standards of ECMAScript 3, which is from 1999. I haven't read through the standard, but it's worth considering the possibility that it has something to do with the issue.
Edit
Here's your problem:
$("#progress-wrapper").find('a.active') ==> []
The first time, there are no a.active elements. Thus, whenever you try to call split on an empty array, it throws a TypeError.
You need to give your first .btn-progress the class active, because the first time around, your first .btn-progress looks like this:
1
There's no active class. Only subsequent .btn-progress elements receive the class active whenever you click the .btn-continue. Your first one never does. Therefore, clickedSlideID[1] and currentSlideID[1] are undefined the first go around. It probably breaks in IE9 because IE9 doesn't understand i < undefined, but it's possible that other more modern browsers go ahead and execute anyway.
Somewhere in the beginning of your code, then, you need to do something like this:
$('.btn-progress').eq(0).addClass('active');
I just tried this in the console on your page, and it worked just fine. After I added the class active to the fist .btn-progress, currentSlideID[1] was now 1, and not undefined.
.full-arrow is an arrow that selects the next page. .full-navigation is a navigation bar, quite simply boxes in a line that change colour when you select them. The rest of the function isn't on here but you get the general idea.
When I create a trigger event to the function below the first one, it goes through okay but I'm unsure whether it's not picking up the index() or whether it's just not working at all. Weirdly, it works the first time but I think that's because the same_page variable is declared as 0 in the beginning.
The reason I'm also doubting whether it's the index() not being passed on is because the alert("foo"); isn't coming up.
$(".full-arrow").click(function() {
$(".full-navigation li:eq(" + same_page+1 + ")").trigger("click");
});
$(".full-navigation li").click(function(event) {
//alert("foo");
//alert(same_page);
same_page = $(this).index();
if(same_page == $(this).index()) { return false; }
});
Where are you getting the same_page variable from? Try using parseInt( same_page, 10 )--I have a hunch it's actually a string.
I have a game I'm creating where lights run around the outside of a circle, and you must try and stop the light on the same spot three times in a row. Currently, I'm using the following code to loop through the lights and turn them "on" and "off":
var num_lights = 20;
var loop_speed = 55;
var light_index = 0;
var prevent_stop = false; //If true, prevents user from stopping light
var loop = setTimeout(startLoop, loop_speed);
function startLoop() {
prevent_stop = false;
$(".light:eq(" + light_index + ")").css("background-color", "#fff");
light_index++;
if(light_index >= num_lights) {
light_index = 0;
}
$(".light:eq(" + light_index + ")").css("background-color", "red");
loop = setTimeout(startLoop, loop_speed);
}
function stopLoop() {
clearTimeout(loop);
}
For the most part, the code seems to run pretty well, but if I have a video running simultaneously in another tab, the turning on and off of the lights seems to chug a bit. Any input on how I could possibly speed this up would be great.
For an example of the code from above, check out this page: http://ericditmer.com/wheel
When optimizing the thing to look at first is not doing twice anything you only need to do once. Looking up an element from the DOM can be expensive and you definitely know which elements you want, so why not pre-fetch all of them and void doing that multiple times?
What I mean is that you should
var lights = $('.light');
So that you can later just say
lights.eq(light_index).css("background-color", "red");
Just be sure to do the first thing in a place which keeps lights in scope for the second.
EDIT: Updated per comment.
I would make a global array of your selector references, so they selector doesn't have to be executed every time the function is called. I would also consider swapping class names, rather than attributes.
Here's some information of jQuery performance:
http://www.componenthouse.com/article-19
EDIT: that article id quite old though and jQuery has evolved a lot since. This is more recent: http://blog.dynatrace.com/2009/11/09/101-on-jquery-selector-performance/
You could try storing the light elements in an array instead of using a selector each time. Class selectors can be a little slow.
var elements = $('.light');
function startLoop() {
prevent_stop = false;
$(elements[light_index]).css('background-color', '#fff');
...
}
This assumes that the elements are already in their intended order in the DOM.
One thing I will note is that you have used a setTimeout() and really just engineered it to behave like setInterval().
Try using setInterval() instead. I'm no js engine guru but I would like to think the constant reuse of setTimeout has to have some effect on performance that would not be present using setInterval() (which you only need to set once).
Edit:
Curtousy of Diodeus, a related post to back my statement:
Related Stack Question - setTimeout() vs setInterval()
OK, this includes some "best practice" improvements, if it really optimizes the execution speed should be tested. At least you can proclaim you're now coding ninja style lol
// create a helper function that lend the array reverse function to reverse the
// order of a jquery sets. It's an object by default, not an array, so using it
// directly would fail
$.fn.reverse = Array.prototype.reverse;
var loop,
loop_speed = 55,
prevent_stop = false,
// prefetch a jquery set of all lights and reverses it to keep the right
// order when iterating backwards (small performance optimization)
lights = $('.light').reverse();
// this named function executes as soon as it's initialized
// I wrapped everything into a second function, so the variable prevent_stop is
// only set once at the beginning of the loop
(function startLoop() {
// keep variables always in the scope they are needed
// changed the iteration to count down, because checking for 0 is faster.
var num_lights = light_index = lights.length - 1;
prevent_stop = false;
// This is an auto-executing, self-referencing function
// which avoids the 55ms delay when starting the loop
loop = setInterval((function() {
// work with css-class changing rather than css manipulation
lights.eq( light_index ).removeClass('active');
// if not 0 iterate else set to num_lights
light_index = (light_index)? --light_index:num_lights;
lights.eq( light_index ).addClass('active');
// returns a referenze to this function so it can be executed by setInterval()
return arguments.callee;
})(), loop_speed);
})();
function stopLoop() {
clearInterval(loop);
}
Cheers neutronenstern
I wanted to write a javascript code that will slide a div in specific direction, distance and in some given time. I wrote this small script. but doesn't work at all. Instead browser gets slow. No change in position is visible.
Can someone tell me how to achieve the result ? I know there are many ready made libraries that can do this easily. But I just wanted to give it a try.
<script type="text/javascript" language="javascript">
var element = '';
var slidePerMS = '';
function slideIt(ele, direction, distance, slideDuration){
element = ele;
var i=0;
slidePerMS = distance / (slideDuration*1000);
for(i=0; i<3000; i++){
setTimeout("changePosition()",1);
}
}
function changePosition(){
var currElement = document.getElementById(element);
currElement.style.left = "'"+slidePerMS+"px'";
}
</script>
SOO many things wrong with that code it's not even funny... Let's see...
You are trying to render a 1,000 FPS animation. This is simply impossible for a browser.
You are passing a string as parameter to setTimeout, which is as evil as eval.
You set slidePerMS once but never change it after, resulting in the div being moved to the exact same spot over and over.
You are setting the style with extra quotes inside - do you put quotes in a CSS file?
That's to name but a few. Try this instead:
<script type="text/javascript" language="javascript">
function slideIt(elem, direction, distance, slideDuration){
var elmt = document.getElementById(elem),
i=0, step = distance / (slideDuration*20),
stepper = setInterval(function() {
i = Math.min(distance,i+step);
elmt.style.left = i+'px';
if( i == distance) clearInterval(stepper);
},50);
}
</script>
You have many problems.
You are treating setTimeout as if it was sleep. Don't do that. It isn't like sleep at all, it runs a function after a given period of time, but doesn't pause the execution of anything else.
This means you just hammer the function repeatedly 3000 times, which is what is locking up the browser.
Instead of using a for loop, you should be using setInterval.
Don't pass a string to setInterval (or setTimeout), it gets evaled, which is slow and hard to debug, and it breaks scope. Pass a function instead.
Inside changePosition you are trying to use a variable called slidePerMS, which is undefined because it is defined in the scope of slideIt.
You are also trying to set left to "'123px'". You can't quote your values in CSS.
Get rid of both the 's.
This is why you can't see any change. Invalid values are ignored in CSS.