jquery animate callback not executing until final loop - javascript

I made a small test code using the jquery transit plugin for animations.
The purpose of the script is to have a photo of a tower make a flip 90 degrees toward the user, switch to another tower image, and flip another 90 degrees to lay flat on the screen. The issue is that after the first 90 degree flip, the image disappears entirely until the for loop has concluded before doing the final flip as the second image. I'm looking to have it flip continuously until the loop is finished.
I imagine this has something to do with closures and scope...
Javascript:
$(function() {
for (var i = 0; i < 10; i++) {
$('#test_flip')
.css('background-image', 'url("tower1.jpg")')
.transition({
rotateY: '90deg'
}, function() {
$('#test_flip')
.css('background-image', 'url("tower2.jpg")')
.transition({
rotateY: '180deg'
});
});
};
});
jsfiddle: http://jsfiddle.net/ce9b9aja/

The issue lies in the fact that the for loop calls .transition 10 times consecutively. The calls are queued in the jQuery queue (which is done behind the scenes by transit.js), but they are not queued in the order you're expecting.
Take the following example:
$(function () {
$('#test').transition({x:40}, function () {
$(this).transition({y:40});
})
$('#test').transition({scale:0.5}, function() {
$(this).transition({skewX:"50deg"});
});
});
#test {
width: 10em;
height: 10em;
background-color: gray;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://ricostacruz.com/jquery.transit/jquery.transit.min.js"></script>
<div id="test"></div>
In this example, the first transition x:40 will be executed instantly because there is no queue. Despite the fact that it is executed instantly, since it is an animation, it will be using some form of setTimeout or setInterval and will not be completed in the transition method call. Consequently, the scale:0.5 transition will be called while the x:40 transition is still animating, which will put it in the queue it before y:40 is put in the queue.
Therefore, the queue order is
x:40 -> scale:0.5 -> y:40 -> skewX:50deg
Similarly, your code is producing the following queue:
rotateY:90deg -> ... -> rotateY:90deg -> rotateY:180deg -> ... -> rotateY:180deg
Thus your code's behavior. It rotates the image 90deg first, then it does it 9 more times, which doesn't change anything visually (hence the "pause"). Then it changes the image and rotates it 180deg and does that 9 more times also.
One solution could be to create a recursive function using the callback of the .transition function. An example is implemented below:
$(function() {
FlipMe($('#test_flip'), "http://i.imgur.com/tYYtwbi.jpg", "http://i.imgur.com/G4CvJpc.jpg", 10)
});
function FlipMe($el, image1, image2, times) {
$el.css('background-image', 'url("'+image1+'")')
.transition({rotateY: '90deg'}, function() {
$el.css('background-image', 'url("'+image2+'")')
.transition({rotateY: '180deg'}, function() {
if(times > 0) {
FlipMe($el, image2, image1, times - 1);
}
});
});
}
Updated fiddle here: http://jsfiddle.net/ce9b9aja/1/
The code above exclusively uses callback functions to dictate the order of events. When the first transition is completed, the callback function will "queue" the next transition, which will be executed instantly since nothing else will be in the queue. And so on.

Related

Interval doesn't clear immediately after repeating for a while

I have a bouncing arrow on my website that I created with Jquery and setInterval, like this:
bouncing = setInterval(function() {
$("div").animate({
top:"30px"
},100,"easeInCubic",function() {
$("div").animate({
top:"0px"
},100,"easeOutCubic");
});
console.log("bounced");
},200);
You can see this in place in a codepen here: http://codepen.io/mcheah/pen/wMmowr
I made it run faster than i needed because its easier to see the issues quicker. My issue is that after leaving the interval running for a few seconds, you'll notice that instead of bouncing back up or down immediately, the bouncing element will stop for half a second and then just hang there, before beginning again. If you leave it running even longer (20 seconds) and then clear the interval, you'll notice that it takes a few seconds to stop bouncing.
My questions are these:
Why does the bouncing go out of sync occasionally?
Why does the clear interval take a while to clear if it's been repeating for a while?
Is there a better way to have a bouncing arrow? Are CSS transitions more reliable?
Thanks for your help!
Your are trying to perfectly coordinate a setInterval() timer and two jQuery animations such that the two come out perfectly coordinated. This is asking for trouble and the two may drift apart over time so it is considered a poor design pattern.
If, instead, you just use the completion of the second animation to restart the first and make your repeat like that, then you have perfect coordination every time.
You can see that here in another version of your codepen: http://codepen.io/anon/pen/NxYeyd
function run() {
var self = $("div");
if (self.data("stop")) return;
self.animate({top:"30px"},100, "easeInCubic")
.animate({top:"0px"}, 100, "easeOutCubic", run);
}
run();
$("div").click(function() {
// toggle animation
var self = $(this);
// invert setting to start/stop
self.data("stop", !self.data("stop"));
run();
console.log("toggled bouncing");
});
It's not a good idea to mix animate() with timers this way. There's NO chance you can synchronize something like this. And there's no need to. You can simply append a function into the animation queue, look here: https://stackoverflow.com/a/11764283/3227403
What animate() does is put an animation request into a job queue which will be processed later, when the right time comes. When you break the interval the stuff that accumulated in the queue will still be processed. There's a method to clear the queue and stop all animation immediately.
The JQuery animation functions actually manipulate CSS, and there is nothing beyond it in HTML. Another option would be using a canvas, but it is a completely different approach and I wouldn't recommend it. With JQuery's animation your already at the best choice.
This is a simple solution to your problem:
function bounce()
{
$("div")
.animate({
top: "30px"
}, 100, "easeInCubic")
.animate({
top: "0px"
}, 100, "easeOutCubic", bounce); // this loops the animation
}
Start bouncing on page load with:
$(bounce);
Stop bouncing on click with:
$("div").click(function() {
$("div").stop().clearQueue().css({ top: "0px" });
// you want to reset the style because it can stop midway
});
EDIT: there were some inaccuracies I corrected now. The running example is on codepen now.
If you want to use javascript for animation you can use something better like the greensock tween library
http://greensock.com/docs/#/HTML5/GSAP/TweenMax/to/
something like this:
var tween = TweenMax.to($("div"), 100, {y: "100px", yoyo: true, repeat: -1});
You could wrap your interval code with:
if(!$("div").is(":animated"))
This will initiate your animation only if your previous one is finished.
The reason why it was bouncing weird is that your animations are queued.
You can check how it works now:
http://codepen.io/luminaxster/pen/XKzLBg
I would recommend using the complete callback when the second animation ends instead and have variable to control a bounce recursive call in this version:
http://codepen.io/luminaxster/pen/qNVzLY

Javascript loop - hide animated div

I'm going to try to make this as descriptive as possible, my apologies in advance if I have difficulties explaining what it is exactly that I am trying to do.
Written Description:
So I am creating a web based game in javascript. This game is a 2 player shooter game. So far, when a player 'shoots' another player, it spawns an animated div which goes from its start (at the gun of player1) to the end of the screen where it then gets removed (using removeChild from the div it was spawned in.)
The problem is that when the bullet 'hits' the player, it jsut keeps on going until the end of the screen. My code does register whether or not the bullet hits the player, but when it does hit the player, I would like it to dissappear either on the player or right after it passes the player, so that it has the effect that it penetrates the player instead of just passing over the player.
Now let me be more specific.
$("#bullet").animate({
marginLeft: '100%'
}, 1000, function(){
document.getElementById("thegame").removeChild("#bullet");
});
basically this is the code that spawns the 'bullet'
now lets say that I wanted to get the bullet to disappear after it's margin passed 70% by doing some sort of loop that checks its position as the animated div is traveling..
how would I do this? (the bullet is #bullet)
I tried doing a loop for this but i must have failed because it didn't work.
Use step option of .animate() , .stop()
var money = $("#thegame span")[0];
$("#bullet").animate({
marginLeft: '100%'
}, {
duration: 1000,
step: function(now, fx) {
// calling `.stop()` triggers `.fail` callback
if (now > 70) $(fx.elem).stop()
},
done: function() {
document.getElementById("thegame").removeChild(money);
},
fail: function() {
document.getElementById("thegame").removeChild(money);
console.log(this, this.style.marginLeft)
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div id="bullet">abc</div>
<div id="thegame"><span>thegame</span></div>
Use setTimeout()for this purpose,
function myfun(){
setTimeout(function(){$("#welcome").show(100)}, 2000);
setTimeout(function(){$("#welcome").hide(500)}, 7000);
}
setInterval()would invoke the supplied code for every time interval that you gave

How to stagger animations and trigger two animations simultaneously on each iteration

I have 3 text boxes. They are hidden by default, and I need them to simultaneously fade in and slide down 40px on DOM load. They also need to be "staggered" such that after the element finishes it's animations the next one triggers.
Here is the relevant js:
jQuery(function($) {
"Use Strict"
var outerFunction = function(dropIn, time, offset) {
$(dropIn).each(function() {
var me = $(this);
var mejo = $(this).children('.drop-in-text-opacity-wrap');
setTimeout(function() {
me.css({'margin-bottom': 0});
mejo.fadeIn(1000);
},time)
time = time + offset;
})
}
outerFunction('.drop-in-text', 0, 500)
});
and a working example on codepen
As you can see I'm most of the way to a solution but when the second and third iterations start, the pevious elements jump back to their original position (though the css doesn't change back when this happens per the inspector).
I've looked at all kinds of answers here on SO using:
.dequeue(),
animate({//some code},{queue: false}),
.stop()
etc
But I'm not getting any love. I'd be grateful for any insight!
Changes I made:
CSS:
Removed margin from drop-in-text.
Added translateY(-40px) to it.
JS:
Changed margin-bottom: 0 to transform: translateZ (0) translateY(0)
CODEPEN

Mousewheel and transition incompatibility

SCENARIO
I have developed a function with jQuery which listens to the user's mouse input (based on the mousewheel plugin).
I analyze the user's input with a function, and alter the default behavior of the mouse, so that it scrolls a given px value with an animation.
PROBLEM
There are div containers in the webpage that transform its size when hovered.
This causes my original mousewheel animation to delay its action for a little time (more or less, half a second). If a div is hovered, and quickly afterwards the mousewheel is rolled, the effect won't run 100% smoothly (it will cause a little lag while the scroll animation is executing, and right afterwards, it will show the animation, which was already running).
If I delete the transition in the containers, the problem is solved. However, I would like to keep the original CSS intact, and run my original animation smoothly.
How can I accomplish this?
JSFIDDLE
http://jsfiddle.net/kouk/z7p0vxpg/
JS CODE
$(function () {
function wheel($div, deltaY) {
if (deltaY == -1) {
var dest = ($(document).scrollTop()+500);
$('html,body').animate({scrollTop: dest}, 1000);
return false;
} else if (deltaY == 1) {
var dest = ($(document).scrollTop()-500);
$('html,body').animate({scrollTop: dest}, 1000);
return false;
}
}
$('html').bind('mousewheel', function (event, delta, deltaX, deltaY) {
if ($('html,body').is(":animated")){
return false;
}
if ( (delta > -2) && (delta < 2) ) {
wheel($(this), deltaY);
event.preventDefault();
console.log(delta);
}
});
});
This is a common problem with the animate() function. The previous animations are in the queue and lagging behind. You should empty the animation queue before starting the next animation to avoid the "lag" feeling.
There are two functions which let you do that : finish() and stop() . I recommend using finish as it will stop the running animation )and remove all queued animations. This was you can immediately start your latest animation.
A user doesn't necessarily want to wait for his previous animation which he has already started a new action.
Here's some sample code:
$('html,body').dequeue().animate({scrollTop: dest}, 1000);
See if the behavior is as your expected now.
And your code (updated with finish()) - http://jsfiddle.net/z7p0vxpg/15/

jQuery Queue Functions

Take these two methods:
function moveUp() {
this.element.rotate(0);
this.element.animate({"margin-top": "-=51px"}, "slow");
//do some more things for instance.
}
function moveRight() {
this.element.rotate(90);
this.element.animate({"margin-left": "+=51px"}, "slow");
}
Just in case, I rotate an object, then move it around with an animation. These functions are mapped to key presses, so if the user presses Up or Right, the object moves. My problem is that the animations are queued properly, because jQuery uses the fx queue for them. But the rotation happens inmediately, as well as any other thing I do, since they are custom things I have in my code.
The consequence is obvious, if you press say up and immediately press right, the object rotates up, but while it is moving upwards, it rotates right, it doesn't really wait until it gets there.
How can I write this code so that the whole methods are chained and not just their animations. I could add callbacks, but they won't always execute together, I need them to be queued if they happen to be called simultaneously, but I don't quite understand how to use the queue functionality in jQuery.
By the way, to see more of the code behind this, I have recently asked this question also: Array of prototype functions
You could animate it using something like:
$elem.animate({rotation: 90},
{
duration: 'slow',
step: function(now, fx) {
$(this).css({ "transform": "rotate("+now+"deg)", "-webkit-transform": "rotate("+now+"deg)", "-moz-transform": "rotate("+now+"deg)" });
}
})
That would add it to the queue automatically
I've made a jsfiddle here:
http://jsfiddle.net/obartra/7Qwgd/1/
EDIT: Just to explain my rationale, I'm guessing that the rotate method you are using is using setTimeout to set the rotation in the CSS progressively, thus not adding it to the animation queue. The code here uses animate to rotate the element so it gets added to the animation queue.

Categories