I have a JS routine (triggered by the user) that can take some time to complete. While the routine is running, I want to show a progress overlay on screen. Here's the code that calls the routine (this is called in response to a click event):
function handleClick() {
$('div#progressOverlay').removeClass('hidden');
myBigRoutine();
...
$('div#progressOverlay').addClass('hidden');
}
The class toggle triggers a change in opacity and visibility (animated with a transition).
The class changes themselves are working fine; the first is executed before the slow routine and the second is executed after everything else.
The issue is that the visual appearance of #progressOverlay doesn't change until after myBigRoutine() finishes.
The result is that the progress overlay flashes on screen for a split second and then is immediately hidden again (all with no animation)
Is there a way to force the visual update/repaint to occur before (or, even better, in parallel with) the big JavaScript routine?
You can bind an event to animation finish. So, when animation finishes, only then your code will execute,
function handleClick() {
$('div#progressOverlay').removeClass('hidden');
}
$("div#progressOverlay").bind("animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd", function(){
myBigRoutine();
...
$('div#progressOverlay').addClass('hidden');
});
I hope, this is what you need :)
Your myBigRoutine() function should be called asynchronously.
function handleClick() {
$('div#progressOverlay').removeClass('hidden');
setTimeout(function() {
myBigRoutine();
...
$('div#progressOverlay').addClass('hidden');
}, 150);
}
150 is a magic number that means timeout delay in milliseconds. It can be any small number.
Also, consider using .hide() and .show() jQuery methods instead of hidden class.
Related
I'm using this Codrops Blueprint to create a quotes rotator. I modified the rotator to have a next button, powered by the code below:
_startRotator: function() {
if (this.support) {
this._startProgress();
}
var timeout = setTimeout($.proxy(function() {
if (this.support) {
this._resetProgress();
}
this._next();
this._startRotator();
}, this), this.options.interval);
$(".testimonial-next").click($.proxy(function() {
clearTimeout(timeout);
if (this.support) {
this._resetProgress();
}
this._next();
this._startRotator();
}, this));
},
I added the .click function below the setTimeout function, and added the var to the setTimeout function.
When .testimonial-next is clicked, the rotator's timer is reset and the code in the timer is instantly executed. As far as I can tell, the timer restarts itself, so I shouldn't have to add code to do this.
On the website where this is put to use (see the "Testimonials" section), however, there seems to be a problem. There should be five quotes, in the order of Kim, Lynn, Shannon, Jennifer, and Chris. If the timer runs without being interrupted, everything works as expected. If the Next button is clicked, certain quotes seem to be skipped. Other times, the quotes stop rotating or randomly rotate at a high speed.
What am I doing wrong?
Your button click handler is attached again and again to the button, making a click result in several executions of the same code.
To solve this you can best move that piece of code out of the _startRotator function and move it somewhere where it will only be executed once.
That way, when you click, there will be only one handler that gets executed.
Less nice, but also a solution, would be to keep the code where it is, but always first remove any existing click handler, before you attach it again:
$(".testimonial-next").off('click').on('click', $.proxy(function() {
// etc...
But this is really an ugly solution. Try to move the code.
I'm creating a slideshow, which will run on a timer (shown by progress bar), but allow users to click arrows to force next. I'm trying to use as much CSS3 as possible, so for my loop timer I'm using the CSS3 animation of the progress bar.
The way it works is that I start my progress bar at width:0, and set it to width:100%;. It has a CSS3 transition of 5s. I then watch for the end of the animation, and use that to call my resetprogress and changeimage functions, after which I then start the progress again. It loops indefinitely.
I've created a jsFiddle, simplified, to show what I'm talking about: http://jsfiddle.net/a3H9L/
Code for the simplified version is below. As you can see, I call startProgress, in which I start the CSS3 animation by changing the width, then set a watcher for the end of said animation, at which point I reset and then start again.
startProgress();
function startProgress() {
$('div').width('100%');
$('div').one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend',function(e){
resetProgress();
startProgress();
});
}
function resetProgress (){
$('div').addClass('notransition'); // Disable transitions
$('div').width('0');
$('div')[0].offsetHeight; // Trigger a reflow, flushing the CSS changes
$('div').removeClass('notransition'); // Re-enable transitions
}
$('button').click(function(event){
resetProgress();
startProgress();
});
My question is, if a user clicks the reset (which will eventually be Next and Previous), how do I break the original loop before resetting and starting a new loop? Right now, I think that I'm starting a new loop without ending the original, which is getting me two loops running at the same time.
EDIT: The reason I think something is wrong is that as I clicked reset a few times, things in the loop start happening at other times besides when the progress is reset.
TL;DR:
Simply call an .off() before chaining your .one()
Let's run an experiment using your fiddle.
Experiment 1:
Using the console to log a simple message everytime the .one() function is called:
$('div').one('...', function(e) {
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
Yes, you are right, they do stack! If you click on the button, more .one() functions are added and will fire together at the same time once the bar reaches the end of the animation. ALL of them will still fire once the animation completes the following and subsequent times!
i.e.: Run fiddle, click your button five times. On completion of the first animation, console logs six messages (5 + 1). The bar resets itself and produces another six more messages. This goes on in multiples of six.
Experiment 2:
Now, let's try turning itself off at the start of the function:
$('div').one('...', function(e) {
$(this).off(e);
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
This didn't produce the cancelling effect we were expecting. Same result as the first experiment.
Experiment 3:
Let's try turning all the handlers off (by omitting the "e"):
$('div').one('...', function(e) {
$(this).off();
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
All queued .one() handlers execute at the end of the animation, but they terminate themselves after running once and do not fire the next time the animation completes.
Experiment 4:
What you actually wanted to do, was to cancel all previously queued handlers before setting a new one. So let's do this:
$('div').off().one('...', function(e) {
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
There's your answer! This function now runs once, as the previous handlers were unset before a new one has been placed. Simply call an .off() before chaining your .one()
Disclaimer:
These experiments assume that those were your only event handlers on your element. If you have additional handlers set by .on(), .one() or similar, instead of using .off() to clear everything, you have to specify which handlers you want to clear, like so:
.off('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend')
I have two div's in my html page
<div id="box1">
</div>
<div id="box2">
</div>
Both of them are absolutely positioned at different positions.
#box1,#box2 {
height: 100px;
width: 100px;
background-color: red;
position: absolute;
}
#box2 {
top :200px;
}
In my Javascript I am animating these two div's as shown below
$(document).ready(function() {
function animateBoxes() {
$("#box1").animate({left : "500px"},5000);
$("#box2").animate({left : "500px"},3000);
}
animateBoxes();
});
When my page loads both the div's start moving at the same time.
My question is if javascript is single threaded how can both the div's move at the same time.
Because the movement of the div's is handled by javascript by two animate functions, one on box1 and other on box2.
How both the animate functions got executed at the same time?
Javascript is single threaded meaning something if is stuck the whole script is stuck...and only way overcome this is spawn worker. To answer your question, Do You Know how animate function of jquery works...it sets timer to call function that updates div's position. So both div get positioned a little bit toward their goal. Timer is provided by javascript and is handled like an event. Javascript has event loop..which is reiterated by browser. Meaning as soon as js is done browser goes through all events and check if they have been fired and then run the function that is associated with them. Here animate function associate itself to timer event and gradually updates div's position, thus looking like it animated. And since it happens in steps the whole js doesn't have to wait for this animation to end to continue executing.
This is how events basically work in js:
browser starts executing the code..
every next action waits till last action is done.
when javascript reaches the code that is attaching function to event, js registers the event, let's say click event. Now it know that button has click event and it has this function.
Now after all code below has been executed browser starts new routine. To check for any event that have been fired.
...looping but no events..
you click button ...it adds that click event has fired to event loop check list.
browser again checks the event loop...it sees the event.
it runs the code for that event and clear it...
suppose you clicked two times...then the code of second event won't start executing till the first is done.
Timer is same but it fires every x amount of time has passed. But as you can see event loops gets stucked executing the function related to the event...the timer is not perfect.
let's say you set timer for 10 ms. And as 9ms had passed you clicked the button starting another event. So your button event starts executing..but it did something really long that took 5 ms.
so your timer actually fires at 14ms.
Take a look at this example: http://jsfiddle.net/82zLC/6/
This will give you idea that animation is divided into chunks which are update step by step...try changing t to 60.
JavaScript can act asynchronous in many situations. .animate() is an example of this. It pretty much has to act asynchronous, in order to not interrupt other page processes. If you are looking for the events to happen one-after-the-other, try looking into callbacks:
$("#box1").animate({left: "500px"},5000, function(){
$("#box2").animate({left: "500px"},5000);
});
When we pass the function to .animate(), it calls the function after it is done with the animation.
I have a function binded to a .click() event. Each time the user clicks, the browser fadeOut() the current element and animate() the marginLeft of the new element. For some reason when I click fast on the button which is binded to this click event, in Chrome it jumpes the animation and just go on directly to the next on (only if I click like 5 times in 1 second) and don't add the marginLeft, in this case it causes a major usability issue for the UI. Is there any fallback for this scenario? Like if the animation is not completed directly add it with css() or something like that?
Thanks
E: The click() event calls a function where all the magic happens, if it helps ..
You may want to look into jquery's .stop() method. It allows you to short-circuit any currently-running animations on an element.
Simple example:
$myElement.on('click', function() {
// first 'true' clears animations in the queue for this element
// second 'true' completes the currently-running animation immediately
$(this).stop(true, true).fadeOut();
});
http://api.jquery.com/stop/
I had some problems with animations with jQuery 1.6. I solved it with jQuery 1.5.
In my project I used setInterval() to make custom logo slider. Animations fired up instantly (not simultaneously) two by two. Everything goes smoothly when I am on the page, but when I went on other tab and comeback (after minute, two or so) to my page project everything goes crazy...
Ok, so I got one answer to use Queue(). Can I achieve same thing with that method?
I have book Manning jQuery in Action and there is nothing on instantly fired up animations with Queue().
Link to Jsfiddle
To quote some of that answer:
Because of the nature of requestAnimationFrame(), you should never queue animations using a setInterval or setTimeout loop.
In general setInterval == BAD and setTimeout == GOOD for animations.
setInterval will try play catchup, as nnnnnn stated:
some browsers may queue everything and then try to catch up when your
tab gets focus again
You best method for looping animate() is by calling recursively, for example:
var myTimeout;
var myAnimation = function () {
$('#myID').animate({
[propertyKey]:[propertyValue]
}, 5000, function() {
myTimeout = setTimeOut(myAnimation, 1000);
});
}
Notice how the myTimeout is held outside the scope of myAnnimation allowing the ability to stop the animation
clearTimeout(myTimeout);
Which you could hook up to the window.unload event.