jQuery: prevent animation and timeout queuing? - javascript

I have probably a simple problem to solve but I don't know what's the right approach on that.
I have a div.notification bar on top of my website that is hidden by default. The bar does get an additional class of either success or warning that sets it to display:block; if needed.
So, there are two cases. Either an output message is rendered directly to the .notification bar on page-load (and it gets a class of success or warning right with it) OR the .notifcation bar is sliding down from top and is fed with json data.
Any way, the notification bar should always be visible for 10 seconds and than slide back up.
Therefore I wrote two functions that handle this bar.
var timing = 10000;
function notificationOutput(type, message) {
var note = $('.notification');
note.hide();
note.find('.message').html(message);
note.stop(true,true).slideDown().delay(timing).slideUp();
}
function notificationSlideUp(slideUp) {
var note = $('.notification');
if ( slideUp ) note.delay(timing).slideUp();
else note.stop(true,true).slideUp();
}
So the notificationOutput() function is triggered from various other functions that return json data and render that into the box.
And the notificationSlideUp() function is right now called on every page load because in my header I have this …
<script type="text/javascript">
$(document).ready(function(){
notificationSlideUp(true);
});
</script>
And there is a reason for that! Remember the case where the .notification is directly set to visible on page-load … e.g. when a user logs-in on my platform the .notification bar is immediately visibile and says "Welcome Username, you've successfully logged in".
I call the notifictaionSlideUp() function in my header to make sure the currently visible .notification bar will slideUp() again after 10 seconds.
And here occurs the problem … 
Somehow this causes the entire notifcation timing and sliding up and down to be "confused". So there must happen some queuing of the slide-functions and or of the delay() function, because without the note.stop(true,true) in the notificationOutput() function the notification wouldn't slideDown() immediately if it's triggered within the first 10 seconds after the page load. That is because the notificationSlideUp() function has already triggered the slideUp() and the delay() of the object.
And the same happens to the delay. If a .notification is put out within the first 10 seconds the delay timing isn't right because the delay counter already started on page-load.
Any idea how to solve that so that it always works?
Update:
var notificationTimer;
function notificationOutput(type, message) {
var note = $('.notification');
note.hide();
note.find('.message').html(message);
note.stop(true,true).slideDown();
clearTimeout(notificationTimer);
notificationTimer = setTimeout(function() {
note.stop(true,true).slideUp();
}, 10000);
}
function notificationSlideUp(slideUp) {
var note = $('.notification');
if ( slideUp ) {
clearTimeout(notificationTimer);
notificationTimer = setTimeout(function() {
note.stop(true,true).slideUp();
}, 10000);
} else {
note.stop(true,true).slideUp();
}
}

Unfortunately, jQuery's delay() function doesn't offer any method for canceling a delay the way setTimeout() does with clearTimeout(). I'd suggest replacing your delay()s with named setTimeout()s, and writing a condition to clearTimeout() for cases in which you need to cancel/trigger a queued animation right away.

http://api.jquery.com/delay/
The .delay() method is best for delaying between queued jQuery
effects. Because it is limited—it doesn't, for example, offer a way to
cancel the delay—.delay() is not a replacement for JavaScript's native
setTimeout function, which may be more appropriate for certain use
cases.
But you can use the following 'hint' - replace delay with some animation which does not change anything. For example with .animate({opacity:1},timing)

Related

fadeOut() callback completes before fading is done

I am working on a font-test page that should switch the font of the header and paragraph tags on click. Which works fine but the experience is really jarring so I want to smooth it out with the following path:
fadeout -> do the font swap -> fade in
But the code runs the font swap first, then the animation.
I have the following code snippet, I am only including the jQuery because it's the root cause of my problem.
// Anytime a card is clicked
$('.card').click(function(){
// Call card by id and switch the fonts
var id = "#" + this.parentElement.id;
$(this).fadeOut(900,swapFont(id));
$(this).fadeIn(500);
// attach the above to some sort of transition callback
});
// Function to swap the font around so that the header is now the body font and the body font is now the header font
function swapFont(id) {
// font-swap logic in here which works fine
}
The issue is because the fadeOut() animation is (effectively) asynchronous. This means the next statement will be evaluated while the animation is in progress - hence the behaviour you're seeing. To fix this you need to use the callback pattern that the fadeX() methods provide.
Also, you're passing the result of the immediate execution of swapFont() to the callback, instead of a function reference to run when the event occurs. Try this:
$('.card').click(function(){
var id = "#" + this.parentElement.id;
$(this).fadeOut(900, function() {
swapFont(id);
$(this).fadeIn(500);
});
});
You are currently passing the result of the swapFont() call, instead you need to point to the function itself.
So this:
$(this).fadeOut(900,swapFont(id));
is the same as:
var x = swapFont(id);
$(this).fadeOut(900, x);
Easiest way is to wrap it in an anonymous function:
$(this).fadeOut(900, function() { swapFont(id) });
Edit:
The fadeIn will also execute before the fadeOut has completed. You can add a .delay or, better, call the fadeIn in the callback as per Rory's answer (so I won't repeat here).

Replay jQuery function every 5 seconds

I want to replay my jquery function ChangeStats() every 5 seconds, it's currently doing sod all.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500);
setTimeout(function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
}, 500);
}
$(document).ready(function(){
setInterval(ChangeStats, 5000);
})();
Yes I have got the right class names.
No I haven't used underscores in my HTML.
I think it's something to do with my use of "find()", once the DOM has loaded and the function is set is it meant to traverse up the DOM tree instead of down?
EDIT:
Updated code, still not working.
HTML:
<span class="admin-stats-big-figures">%productCount%</span>
<span class="admin-stats-big-figures-hidden">hey</span>
Ok, I am going to go out on a limb and make several assumptions here; one is that you wish to cycle between two elements repeatedly, another is that you are using $(this) in the context of the window rather than a containing element. If either of these are incorrect then the following solution may not be suitable. However, let's give this a shot, eh?
1) You need to use setInterval rather than setTimeout to create a repeating call. You can of course "chain" your timeouts (ie: call the succeeding timeout from the code of the current timeout). This has some benefits in certain situations, but for now let's just assume you will use intervals rather than timeouts.
2) You call the find() jQuery method every time, which is a little unnecessary, especially if you will be repeating the actions so one idea would be to cache the lookup. If you are going to do that a custom object would be more suitable than separate global variables.
3) Some flexibility in terms of starting and stopping the animation could be provided. If we use a custom object as mentioned in (2) then that can easily be added.
4) You are using fadeIn and fadeOut, however if you wish the items to cycle then fadeToggle may be your best solution as it will simply allow you to do exactly that, toggle, without needing to check the current opacity state of the element.
5) Finally in my example I have provided a little extra "padding HTML" in order for the example to look good when run. Fading in jQuery will actually set the faded item to a CSS display of "none" which results in the content "jumping about" in this demo, so I have used some div's and a couple of HTML entity spaces to keep the formatting.
Ok, after all that here is the code..
// your custom animation object
var myAnim = {
// these will be cached variables used in the animation
elements : null,
interval : null,
// default values for fading and anim delays are set to allow them to be optional
delay : { fade: 500, anim: 200 },
// call the init() function in order to set the variables and trigger the animation
init : function(classNameOne, classNameTwo, fadeDelay, animDelay) {
this.elements = [$("."+classNameOne),$("."+classNameTwo)];
// if no fade and animation delays are provided (or if they are 0) the default ones are used
if (animDelay) this.delay.anim = animDelay;
if (fadeDelay) this.delay.fade= fadeDelay;
this.elements[0].fadeOut(function(){myAnim.start()});
},
// this is where the actual toggling happens, it uses the fadeToggle callback function to fade in/out one element once the previous fade has completed
update : function() {
this.elements[0].fadeToggle(this.delay.anim,function(el,delay){el.fadeToggle(delay)}(this.elements[1],this.delay.anim));
},
// the start() method allows you to (re)start the animation
start : function() {
if (this.interval) return; // do nothing if the animation is currently running
this.interval = setInterval(function(){myAnim.update()},this.delay.fade);
},
// and as you would expect the stop() stops it.
stop : function () {
if (!this.interval) return; // do nothing if the animation had already stopped
clearInterval(this.interval);
this.interval = null;
}
}
// this is the jQuery hook in order to run the animation the moment the document is ready
$(document).ready(
function(){
// the first two parameters are the two classnames of the elements
// the last two parameters are the delay between the animation repeating and the time taken for each animation (fade) to happen. The first one should always be bigger
myAnim.init("admin-stats-big-figures","admin-stats-big-figures-hidden",500,200);
}
);
OK, so now we need the HTML to compliment this (as I say I have added a little formatting):
<div><span class="admin-stats-big-figures">One</span> </div>
<div><span class="admin-stats-big-figures-hidden">Two</span> </div>
<hr/>
<input type="button" value="Start" onclick="myAnim.start()"/> | <input type="button" value="Stop" onclick="myAnim.stop()"/>
I have also provided buttons to stop/start the animation. You can see a working example at this JSFiddle - although the stop/start buttons are not working (presumably something specific to JSFiddle) they do work when in context though.
Here im gonna just replace your $(this). and maybe it'll work then.. also using callback.
function ChangeStats() {
$('body').find('.admin-stats-big-figures-hidden').fadeIn(500, function() {
$('body').find('.admin-stats-big-figures').fadeOut(500);
});
}
$(document).ready(function(){
setTimeout('ChangeStats()', 5000);
});

Javascripts "setInterval()" used with jQuery animations results in a function queue up

What's happening
Okay, so I've made a function on my front page where some products are shown besides each other, fading out, changing products and fading back in every 5 seconds. The code looks like this:
var t;
$(document).ready(function(){
// Some pre-animation stuff, like loading the products etc.
// Initialize animation timer
t = setInterval("changeProducts();", 5000);
};
function changeProducts() {
// Fade out
$("#anfBox").fadeTo(200, 0.01, function() {
// Change products
// Fade back in
$("#anfBox").fadeTo(200, 1);
});
}
It all looks fine and runs as it should, except when i go to another window for a minute and then comes back, the changeProducts function is executed rapidly a few times (depending on how long i've been away). The products fade out, change fades in again, and then repeats instantly, where there should be a ~5 second delay.
What I've tried
So what I think I need to do is use something like clearInterval(t) when the focus is lost from the window, then re-initialize the timer when the window is re-entered, i just don't know how to do that, and i'm having a hard time finding anything useful on google.
I'm thinking maybe there's allso a way to run the animations even if the window is not in focus to avoid the function-queue.
I've also tried using setTimeout() instead but with no luck.
Any ideas on how to avoid the animation-queue is much appreciated.
With the latest browser updates lots of browsers now stop execution of code when current page or tab is not active. When you return that tab/window it executes all of queued actions and you see a rush of effect running each after.
Simply check effect queue before apply another effect, if queue's length is 0 then apply.
Try this (example);
var t;
function changeProducts() {
// Fade out
var $anfBox = $("#anfBox"),
queue = $anfBox.queue('fx');
if (queue && queue.length === 0) {
$anfBox.fadeTo(200, 0.01, function() {
// Change products
// Fade back in
$anfBox.fadeTo(200, 1);
});
}
}
$(document).ready(function() {
// Some pre-animation stuff, like loading the products etc.
// Initialize animation timer
t = setInterval(changeProducts, 5000);
});

jQuery Fadeout on Click or after delay

I am displaying a message box on a website. I would like to be able to have it either fadeout on click or after X seconds. The problem is that the delay() function takes the place over the click() function making it so even if you click close you still have to wait the time.
Here is the jQuery
$(document).ready(function() {
$(".close-green").click(function () {
$("#message-green").fadeOut("slow");
});
//fade out in 5 seconds if not closed
$("#message-green").delay(5000).fadeOut("slow");
})
I also set up a simple jsfiddle. To see the problem comment out the delay line http://jsfiddle.net/BandonRandon/VRYBk/1/
You should change it to a setTimeout:
http://jsfiddle.net/VRYBk/3/
(in the jsfiddle link)
I removed your delay line and replaced it with a standard setTimeout like:
setTimeout(function(){
$("#message-green").fadeOut("slow");
},5000)
As a note of WHY, is because JS is read top to bottom and it'll read your delay before you click and trigger the event. Therefore, even when you click the delay is being run causing all animation to pause.
This would be an ideal use for jQuery 1.5's new Deferred objects:
// a deferred object for later processing
var def = $.Deferred();
// resolve the deferred object on click or timeout
$(".close-green").click(def.resolve);
setTimeout(def.resolve, 5000);
// however the deferred object is resolved, start the fade
def.done(function() {
$(".message-green").fadeOut("slow");
});
Working demo at http://jsfiddle.net/Nyg4y/3/
Note that it doesn't matter that if you press the button the timer still fires - the second call to def.resolve() is ignored.
I fount it the best workaround suggested by Oscar Godson, I somehow added this to it:
if (! $clicked.hasClass("search"))
{
setTimeout(function()
{
jQuery("#result").delay('1500').fadeOut('2800');
},7000);
}
});
His original suggestion is very useful:
You should change it to a setTimeout: http://jsfiddle.net/VRYBk/3/
(in the jsfiddle link)
I removed your delay line and replaced it with a standard setTimeout like:
setTimeout(function(){
$("#message-green").fadeOut("slow");
},5000)
By Oscar Godson,

A sticky situation for jQuery slideshow

I'm required to develop a slideshow (not an existing one) with jQuery. I was able to change picture with a function that I created named changePic (takes an image link). It incorporates the fading animation from the jQuery library.
For the slideshow I'm trying to use a while loop. It kind of works, except that it doesn't wait for the animation to finish.
How do I, a) wait for the animation to finish, b) delay the changing picture so it display the picture for a couple of seconds?
Also tried Settimeout, and it doesn't work.
Edit:
Basically changing image is like this:
function changePic(imglink){
var imgnode = document.getElementById("galleryimg");
$(imgnode).fadeTo(500, 0, function(){
$(imgnode).attr("src", imglink);
$(imgnode).fadeTo(1000, 1);
})
}
and the slideshow code is like this, but obviously it shouldn't.
function slideshow(gallerylinks){
var i=0;
while (i<gallerylinks.length){
changePic(gallerylinks[i]);
i++;
}
}
You could always try ditching the while loop, and going with a perpetually recursive function...
on the .animate, you could add a timeout function (at whatever interval) that calls the changePic function. As I have no idea what your code looks like, I will provide a fantastically generic outline.
/* array of imgUrls */
var imgUrls = new Array(); //populate it however
changePic(slideToShowIndex, fadeOutSpeed, fadeInSpeed, slideDelay)
{
$('#slideHolder').animate({ opacity: 0}, fadeOutSpeed , function(){
$('#slideHolder').attr('src', imgUrls[slideToShowIndex]);
$('#slideHolder').animate({ opacity: 1 }, fadeInSpeed, function() {
setTimeout(function() { changePic(slideToShowIndex+1, fadeOutSpeed, fadeInSpeed, slideDelay);}, slideDelay});
});
}});
}
$(document).ready(function() {
changePic(0, 5000, 5000, 10000);
});
This should (in theory) fade the image out, swap it with the new one, and fade it in (both taking 5 seconds) and then adding a delay to call itself with the next slide index in 10 seconds.
This is in no way perfect, but does outline the general idea. Since we have no idea what your code looks like, I can only assume your setTimeout was in the wrong spot. Doing it like this will make sure that the animation has finished before the timeout is set. This guarantees that the slide wont change until after the animation has changed.
of course you could always use a combination of the ':not(:animated)' selector and a setInterval to achieve much the same effect.
EDIT: made a slight change to stack the animations properly. The thoery behind this still works even with the OPs addition of code.
You could have provided more details or example code but have a look at stop() and delay() functions.

Categories

Resources