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.
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.
According to jQuery document on .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.
Could someone please expand on this? When is it more appropriate to use .delay(), and when is it better to use .setTimeout()?
I think what you posted explains itself really.
Use .delay() for jQuery effects including animations.
setTimeout() is best used for everything else. For example when you need to trigger an event at a certain elapsed time.
As far as I understand, .delay() is meant specifically for adding a delay between methods in a given jQuery queue. For example, if you wanted to fade an image into view during the span of 1 second, have it visible for 5 seconds, and then spend another second to fade it out of view again, you could do the following:
$('#image').fadeIn(1000).delay(5000).fadeOut(1000);
In this instance, .delay() is more intuitive to use since it is specifically meant for delaying events in a given jQuery queue. setImeout(), on the other hand, is a native JavaScript method that isn't intended explicitly for a queue line. If you wanted an alert box to pop up 1 second after clicking on a button, you could do the following:
function delayAlert() {
var x = setTimeout("alert('5 seconds later!')", 5000);
}
<input type="submit" value="Delay!" onclick="delayAlert();" />
You can use delay with animations, for example:
$('.message').delay(5000).fadeOut();
You can also use timeOut to delay the start of animations, for example:
window.setTimeout(function(){
$('.message').fadeOut();
}, 5000);
As you see, it's easier to use delay than setTimeout with animations.
You can delay pretty much anything with setTimeout, but you can only delay animations with delay. Methods that aren't animations are not affected by delay, so this would not wait a while before hiding the element, it would hide it immediately:
$('.message').delay(5000).hide();
.delay() is mostly used for chaining together animation effects with pauses in between.
As the docs mention, there is no way to cancel the delay. In the case where you may want to cancel the delay, a setTimeout() should be used instead so you can cancel it with clearTimeout()
Another side effect of delay(): it seems to disable the ability to hide (or fadeOut, etc) the objecting being delayed, until the delay is over.
For example, I set up the following code (perhaps a stackoverflow developer will recognize the css names....) to hide a 'div':
$j(document).ready(function(){
var $messageDiv = $j("<div>").addClass('fading_message')
.text("my alert message here").hide();
var $closeSpan = $j("<span>").addClass('notify_close').text("x");
$closeSpan.click(function() {$j(this).parent().slideUp(400);});
$messageDiv.append($closeSpan);
$j('.content_wrapper_div').prepend($messageDiv);
$messageDiv.fadeTo(500, .9).delay(5000).fadeTo(800,0);
});
Clicking the "x" that's in the span (that's in the 'div') did fire off the click function (I tested with an alert in there), but the div didn't slideUp as directed. However, If I replace the last line with this:
$messageDiv.fadeTo(500, .9);
..then it did work - when I clicked the "x", the surrounding div slideUp and and away. It seems as if the background running of the "delay()" function on the $messageDiv "locked" that object, so that a separate mechanism trying to close it couldn't do so until the delay was done.
I'm having problems getting standard javascript timers to clear on mobile devices (Android and iOS, phone and tablet).
My page contains 2 buttons, a play/pause toggle and a stop button (both FontAwesome icons), the simple HTML for which is:
<span class="fa fa-pause control-button" id="pause-button"></span>
<span class="fa fa-stop control-button" id="stop-button"></span>
The interval is initiated with the following function:
var interval = function() {
$('.control-button').fadeIn(300);
//initiate the interval
infiniteInterval = window.setInterval(Tiles.infiniteTick, speed);
};
Where speed is defined in an earlier function (default is 300). infiniteTick is a very simple function which is working fine. I haven't explained it here as it would require an explanation of the whole program but I can provide code if required.
The play and pause toggles are as follows:
$('body').on('click touchstart', '#pause-button', function() {
if ($(this).hasClass('fa-pause')) {
window.clearInterval(infiniteInterval);
$(this).removeClass('fa-pause');
$(this).addClass('fa-play');
} else {
infiniteInterval = window.setInterval(Tiles.infiniteTick, speed);
$(this).removeClass('fa-play');
$(this).addClass('fa-pause');
}
});
Finally, the interval is terminated with this (some purely aesthetic extras removed for simplicity)
$('body').on('click touchstart', '#stop-button', function() {
window.clearInterval(infiniteInterval);
$('.control-button').fadeOut(300);
});
I initially thought from researching this that it was due to click events not being properly registered, but as you can see I have added touchstart to all the click events and that has made no difference. It's working absolutely fine on all desktop browsers.
Any help is greatly appreciated, and I'd be happy to answer any further questions.
Thanks,
Ben
I've managed to fix the problem, which it turns out was twofold.
Firstly, the click event was firing twice. This was fixed using this SO question: jquery mobile click event fires twice
Secondly, I wasn't properly clearing the intervals.
Edited with #MjrKusanagi's comments
A simple call to clearInterval() before every setInterval() call has fixed the problem, making sure that the interval was always reset before starting again.
Original sketchy workaround:
I've called
infiniteInterval = null;
after every clearInterval() call, as well as wrapping the setInterval() calls with
if (infiniteInterval === null)
Thanks to everyone who commented and hopefully this will help someone sometime :)
First, your click event is firing twice because of this sentence:
$('body').on('click touchstart', '#pause-button', function() { ...
It listens to two events click and touchstart, thus it will be triggered twice, once on click event and once on touchstart event. This is also why your code works well on pc because there's no touchstart event in pc browsers.
So every time you touch that button, things happened like this:
1st event triggered
interval set, handle id is 1 (for example)
infiniteInterval = 1
2nd event triggered
another interval set, handle id is 2
infiniteInterval = 2
And now there's two timing cycles running instead of one, and you only have track of the second one. When you invoke clearInterval, only the handle id = 2 interval is cleared, and 1 is still running.
So the solution is:
Fix the twice-triggered events problem, by only listen to click. (try fastclick or zepto or other lib to deal with the click latency on mobile devices)
As your own answer said, set infiniteInterval to null, and if it is not null do not ever start another interval. (I think it is more elegant than "always clear before setting" works, as infiniteInterval works as a flag of running interval)
Hope these could solve your problem.
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'm trying to trigger a function when the window is scrolled more than a certain number of pixels.
Here's my code:
$(window).scroll(function(){
if( $(this).scrollTop() >= 100 ) {
someFunction();
} else {
someOtherFunction();
}
});
It kinda works, but there's either a delay of around 2-4 seconds after scrolling before the function(s) are fired or else the functions aren't triggered at all.
Tried it out in Safari / Chrome. Don't know if that helps or not!
The code looks fine and works for me.
As Wolfram says, it's rarely a good idea to attach handlers directly to the scroll event, as this fires a lot and can bring the user's system to a crawl.
I'd recommend using Ben Alman's jquery throttle/debounce plugin.
It works using jQuery 1.6.1 + mousewheel / scrollbar in Chrome15/Safari5.1/FF7 on OSX. What are you doing in those two functions? For testing, I put a simple alert() in someFunction and nothing in someOtherFunction.
Remember that one of those functions is executed every time the scroll-event fires unless you stop it once it was called... e.g. someFunction is called a lot after you scrolled below the 100px line.
John Resig: It's a very, very, bad idea to attach handlers to the window scroll event.
If you're scolling by holding in the click-button instead of using the scroll wheel, I believe the event won't fire until you release the click-button.
Have you considered running a loop that checks the scrollTop instead?
EDIT:
I just check an old project of mine using window scroll event, and it runs perfect with the same event.
I asume you have this script of yours wrapped inside:
$(function() {
// code
});