jQuery animation inside recursive function very slow - javascript

I have a small animation of an arrow bouncing horizontaly over three car pictures. The arrow starts with 200ms per bounce and the time increases by 200ms each turn, until it stops on 7th turn on car #3.
It works on Chrome and Firefox almost smoothly. On Safari 7 it starts fast and after two laps it becomes very slow and skips a lot of frames.
The Javascript code is the following:
var fwd = true;
var cnt = 6;
var time = 200;
function play(){
var tgt = fwd ? '310px' : '10px';
$('#arrow').animate({left: tgt}, time, function() {
if (cnt > 0){
cnt--;
fwd = !fwd;
time += 200;
play();
} else {
finalTarget();
}
});
}
function finalTarget (){
$('#arrow').animate({left: '230px'}, 466, function(){
$('#car3').hide(0).show('pulsate', {times: 3}, 600, function(){
$('#car1, #car2').fadeTo('slow', 0.3);
});
});
}
The code is also on JSFiddle http://jsfiddle.net/MkeeE/1/
What's the problem with this code?
Am I not supposed to call the "play" function inside the callback function?
EDIT:
As #jfriend00 pointed on comments, there's no recursion on this code. "By the time the animation completion function is called and play() is called again, the original invocation of play() has long since completed."

The typical method of doing a 'looping' animation style is to alternate animate functions in the call back.
I modified the fiddle you provided: http://jsfiddle.net/Culyx/fLz5U/4/
Jquery:
cnt = 6;
var arrowSpeed = 400;
bounceLeft = function(){
$("#arrow").animate({left: "+=380px"},{duration:arrowSpeed, complete: bounceRight});
}
bounceRight = function(){
cnt--;
if(cnt>0){
$("#arrow").animate({left: "-=380px"},{duration:arrowSpeed, complete: bounceLeft});}else{
finalTarget();
}
}
bounceLeft();
function finalTarget (){
$('#arrow').animate({left: '230px'}, 466, function(){
$('#car3').hide(0).show('pulsate', {times: 3}, 600, function(){
$('#car1, #car2').fadeTo('slow', 0.3);
});
});
}
The animation is the same but I think i misjudged the animation width; Also switched it to on Document.Ready can easily be switched back to function with a button.

Related

Vanilla Js: Start a while loop in one event, break it with a second event? ('mouseover' start, 'mouseout' sets loop condition to false)

I'd like to repeat an animation that begins in a 'mouseover' event until a 'mouseout' event is triggered. I've tried wrapping the 'mouseover' action in a while loop after giving its condition global scope, then setting its condition to false in the 'mouseout' callback, but the while loop doesn't break. I tried assigning the 'mouseout' both outside and inside of the while loop, with the same result.
Is there an event-driven way to break a while loop that had begun in another event's callback? I'm curious about this question in general, and specifically, how to apply a solution to the context of 'mouseover' and 'mouseout' events. Thanks!
Also: The snippet below works as-is. I commented out the faulty while loop logic. The animation should, if it worked as I wanted, keep radiating out gold rings from the same center on a Google Map, till the mouseout occurs. Minus the while loop, it works, but only once.
var haloRad = 1;
var center = {lat:20.0000,lng:20.0000};
var hrefHalo = new google.maps.Circle({
'strokeColor' : 'RGB(255,255,100)',
'strokeOpacity': 1,
'strokeWeight' : 3,
'fillColor' : 'RGB(255,255,100)',
'fillOpacity' : 0,
'map' : null,
'center' : center,
'radius' : haloRad,
});
// var isOver;
a.addEventListener('mouseover', function(){
// isOver = true;
hrefHalo.setMap(map);
// while(isOver){
var i = 1;
for (; i <= 10; i++){
(function animateHalo(i){
setTimeout(function(){
hrefHalo.setRadius(haloRad * (1 + (i / 10)));
}, i * 75)
})(i)
}
// }
});
a.addEventListener('mouseout', function(){
// isOver = false;
hrefHalo.setMap(null);
});
I have created two different ways you can do this.
Using JavaScript and the "hackish" way of canceling setInterval
Using Jquery and animate and its stop() function.
This fiddle shows both in action. Implement either into your code, I do not have you HTML or CSS to replicate your problem.
https://jsfiddle.net/6oL7xg5n/2/
var myVar;
function myFunction() {
var h = $(".orange").height();
myVar = setInterval(function(){
console.log("hllo");
$(".orange").height(h);
h++;
}, 30);
}
function myStopFunction() {
clearTimeout(myVar);
}
$(".orange").on("mouseover", function() {
myFunction();
});
$(".orange").on("mouseout", function() {
myStopFunction();
});
// Jquery version
$(".apple").on("mouseover", function() {
// Used "1000px" but you can use a different value or a variable
$(this).animate({height: "1000px"}, 8000);
});
$(".apple").on("mouseout", function() {
$(this).stop();
});

Pausing Recursive Function Call

I have a function of which I'm supposed to pause on mouseenter and pause on mouseleave but the problem is that the function is recursive. You pass in a parent and index and it will recursively loop through each inner div displaying and hiding. The function looks like this:
var delay = 1000;
function cycle(variable, j){
var jmax = jQuery(variable + " div").length;
jQuery(variable + " div:eq(" + j + ")")
.css('display', 'block')
.animate({opacity: 1}, 600)
.animate({opacity: 1}, delay)
.animate({opacity: 0}, 800, function(){
if(j+1 === jmax){
j=0;
}else{
j++;
}
jQuery(this).css('display', 'none').animate({opacity: 0}, 10);
cycle(variable, j);
});
}
I've tried setting a timeout and clearing it but it doesn't seem to do anything (it seems to ignore the timeout entirely), I've tried using stop() and calling the function again on mouseout but that seemed to repeat the function call (I was seeing duplicates) and it stopped mid animation which didn't work. I tried adding in a default variable at one point (var pause = false || true;) but I also couldn't get it to work as expected (though I feel the solution relies on that variable). I'm open to all suggestions but there are some rules of engagement:
Rules: There can't be any major changes in how this function works as many things rely on it, it's something I do not have control over. Assume the function call looks like this jQuery('#divList', 0) and holds a bunch of div elements as children.
The timeout function is the last solution I tried which looks like:
jQuery('#divList').on('mouseenter', function(){
setTimeout(cycle, 100000);
})
.on('mouseleave', function(){
window.clearTimeout();
});
Perhaps something like this? I simplified the animation just to make the example simpler, but you should be able to adapt it to your needs.
First, we have a function that's responsible for animating a set of elements. Every function call returns a new function that allows to toggle the animation (transition between pause and resume).
function startCycleAnimation(els) {
var index = 0,
$els = $(els),
$animatedEl;
animate($nextEl());
return pauseCycleAnimation;
function animate($el, startOpacity) {
$el.css('opacity', startOpacity || 1)
.animate({ opacity: 0 }, 800, function () {
animate($nextEl());
});
}
function $nextEl() {
index = index % $els.length;
return $animatedEl = $els.slice(index++, index);
}
function pauseCycleAnimation() {
$animatedEl.stop(true);
return resumeCycleAnimation;
}
function resumeCycleAnimation() {
animate($animatedEl, $animatedEl.css('opacity'));
return pauseCycleAnimation;
}
}
Then we can kick-start everything with something like:
$(function () {
var $animationContainer = $('#animation-container'),
toggleAnimation = startCycleAnimation($animationContainer.children('div'));
$animationContainer.mouseenter(pauseOrResume).mouseleave(pauseOrResume);
function pauseOrResume() {
toggleAnimation = toggleAnimation();
}
});
Example HTML
<body>
<div id="animation-container">
<div>Div 1</div>
<div>Div 2</div>
<div>Div 3</div>
</div>
</body>
If you want something more generic, it seems there's a plugin that overrides animate and allows to pause/resume animations in a generic way.
You will need to put a flag that each cycle checks before it determines if it is going to run. Then you can just change that flag when the mouse events are triggered. If you need to pick up where you left off when you unpause, consider saving the last value of j
function cycle(variable, j){
if (window.paused) {
window.last_j = j;
return;
}
...
Then when you want to pause, just set window.paused = true . To resume, change it back to false and call cycle again:
cycle(variable, last_j);

While MouseDown, first slowly decrease number, then increase decreasing speed (jQuery)

As the title suggests I'm stuck with a MouseDown problem.
What I want in "pseudocode"
While ("#arrowUp").mouseDown(function() {
counter++; //One time directly when pressed
if(MouseDownTime > 500){ //500ms that is
setTimeOut({counter++}, 75); //Meaning, every 75ms counter++
}
}
I have been looking around at Stack Overflow for over two days now. And I succeeded to increment every 75ms, but then I couldn't build in the if(MouseDownTime > 500)-statement, while still being able to increase the counter every 75ms after the 500ms.
$("#tempUp").mousedown(function() { //When longer pressed, automatically faster increase Temperature
int = setInterval(editTemp(currTemp+1), 250);
})
.mouseup(function() {
clearInterval(int);
numberOfRepeats = 0;
});
This is code I have of of my function so far. Could anyone help me out? Or am I asking the question in a wrong way? (non-constructive)
If I understand you correctly you can make use of a combination of setTimeout and setInterval, like so:
$(document).ready(function ()
{
var temp = 0;
var to = null;
var iv = null;
$("#ClickMe").on("mousedown", function ()
{
temp++;
$("#Temp").html(temp);
to = setTimeout(function ()
{
iv = setInterval(function ()
{
temp++;
$("#Temp").html(temp);
}, 75);
}, 500);
}).on("mouseup mouseleave", function ()
{
clearTimeout(to);
clearInterval(iv);
});
});
See this FIDDLE for an example.
EDIT: Added the mouseleave event as well as suggested by José F. Romaniello.

Synchronizing jQuery setInterval for slideshow

I created a very basic slideshow by repeating the jQuery effects with setInterval. However, there is a mismatch in timing after each cycle of the setInterval. After a few cycles, it leads to a visible mismatch in two parallel slide effects.
The code is
$(document).ready(function(){
var frontslides =$("#first li").length,
slideTime=500,
slideCycle=parseInt(slideTime*frontslides);
function frontSlide(){
$("#first li").each(function(n){
$(this).delay(slideTime*n).fadeIn(400).fadeOut(100);
$("#second li").eq(n).delay(slideTime*n).fadeIn(400).fadeOut(100);
});}
frontSlide();setInterval(frontSlide, slideCycle);});​
The working example is here
To save your valuable time, its speed is fast, but it happens on any speed. After a few cycles, you can see that left and right slides are no longer synchronized.
First approach: setTimeout
You could move the transitioning to the next slide in a separate function and call it with setTimeout. Then, you just store the slide number in a variable and increment it after each function call.
$(document).ready(function(){
var firstSlides = $("#first li"),
secondSlides = $("#second li"),
nbSlides = firstSlides.length,
slideTime = 500,
nextSlide = 0;
function slideshow() {
firstSlides.eq(nextSlide).fadeIn(400).fadeOut(100);
secondSlides.eq(nextSlide).fadeIn(400).fadeOut(100);
nextSlide = (nextSlide+1) % nbSlides;
setTimeout(slideshow, slideTime);
}
slideshow();
});
Here's a fiddle of this first approach.
Second approach: promises
If you want to absolutely guarantee that both animations are completed when starting the next animation, you can use the new promises from jQuery 1.6. You can call $.promise() on both jQuery objects to get a promise which will be resolved when all animations of that element are completed. Then, you can set up a master promise with $.when(promises...) which will be resolved when all the given promises are resolved and set up a then handler.
$(document).ready(function(){
var firstSlides = $("#first li"),
secondSlides = $("#second li"),
nbSlides = firstSlides.length,
fadeInTime = 400,
fadeOutTime = 100,
nextSlide = 0;
function slideIn() {
var p1 = firstSlides.eq(nextSlide).fadeIn(400).promise();
var p2 = secondSlides.eq(nextSlide).fadeIn(400).promise();
$.when(p1, p2).then(slideOut);
}
function slideOut() {
var p1 = firstSlides.eq(nextSlide).fadeOut(100).promise();
var p2 = secondSlides.eq(nextSlide).fadeOut(100).promise();
nextSlide = (nextSlide+1) % nbSlides;
$.when(p1, p2).then(slideIn);
}
slideIn();
});
For a simple slideshow, you probably won't notice much of a difference. However, with promises you can make much more advanced transition sequences with funky timings while still maintaining synchronisation.
Here's a fiddle of that approach.
Set the animations to both run at the "same" time:
function frontSlide(){
$("#first li").each(function(n){
var _ = $(this);
setTimeout(function() {
_.fadeIn(400).fadeOut(100);
$("#second li").eq(n).fadeIn(400).fadeOut(100);
}, slideTime*n);
});
}
Since I opened my mouth, here is the version I came up with. I added a blocked flag in there to indicate an effects loop was currently running, hopefully preventing the script from overrunning itself. I also added a factor value so that if overrunning did occur, it would loop more quickly, to preserve the appearance that the loop was not disrupted.
If you have any other questions about it, just let me know. It's pretty straight forward.
$(document).ready(function() {
var $sel = $('#left, #right').find('li:first-child'),
slidetime = 200,
timein = 400,
timeout = 100,
blocked = false;
var fadeout = function() {
$sel.fadeOut(timeout, next);
};
var next = function() {
if ($sel.is(':last-child')) {
$sel = $sel.siblings(':first-child');
} else {
$sel = $sel.next();
}
blocked = false;
};
(function slider() {
var factor = .2;
if (!blocked) {
factor = 1;
blocked = true;
$sel.fadeIn(timein, fadeout);
}
setTimeout(slider, slidetime * factor);
})();
});​
http://jsfiddle.net/j63vH/

Stopping increment at specific height

I am animating images within a logo in a slot-machine type of animation. I need it to stop animating once it gets to the top of the image (and send a callback if possible).
Currently, this is how I'm accomplishing the animation:
window.setInterval(function() {
$('#title-1 img').animate({bottom : '-=60px'})
}, 5000);
Any ideas on how I would get it to stop, and to send the callback?
So I assume you have a sprite image containing multiple logos, you want them to slide each 5 seconds until you reach the last one, and then call the callback?
var cnt = 6,
$img = $('#title-1 img'),
i = 0;
function animate_logo(cb) {
if (i < cnt) {
$('#title-1 img').animate({bottom : '-=60px'});
i += 1;
setTimeout(function () {animate_logo(cb)}, 5000);
}
else {
cb();
}
}();
var interval = window.setInterval(function() {
$('#title-1 img').animate({bottom : '-=60px'},
function(){
if(`some stop point`) clearInterval(interval);
}
);
}, 5000);
I would not suggest using a setInterval when dealing with animations due to the way newer browsers are making changes to the way setInterval and setTimeout work when the tab is not the active tab.
var $title1 = $("#title-1");
var $title1img = $title1.find('img');
function anim(){
if ($title1.height() < parseInt($title1img.css("bottom"))) {
setTimeout(function(){
$title1img.animate({bottom : '-=60px'},anim);
},5000);
}
}
$title1img.animate({bottom : '-=60px'},anim);
Edit: another reason not to use setInterval to fire off animations is due to the reqeustAnimationFrame that was implemented in 1.6 and removed in 1.6.3, which will more than likely be added back in 1.7. If you write code now that will be compatible later, that's less maintenance you will have to do later if you end up being required to upgrade.
Here's a jsfiddle http://jsfiddle.net/czUnU/
Edit: function...
function animColumn(title,img){
function anim(){
if (title.height() < parseInt(img.css("bottom")) {
setTimeout(function(){
img.animate({bottom : '-=60px'},anim);
},5000);
}
}
img.animate({bottom : '-=60px'},anim);
}
animColumn($("#title-1"),$("#title-1 img"));
animColumn($("#title-2"),$("#title-2 img"));
animColumn($("#title-3"),$("#title-3 img"));
http://jsfiddle.net/czUnU/1/

Categories

Resources