jQuery .animate() loop produces unexpected offset over time - javascript

I have the following Javascript code (using jQuery):
floatUpAndDown();
function floatUpAndDown() {
$("#bird").animate({top: '+=30px'}, 1400)
.animate({top: '-=30px'}, 1400, 'swing', floatUpAndDown);
}
#bird is an img tag that is absolutely positioned. Simply by looking at the code above, one should think that the bird would move 30px down, then back up 30px, and then loop forever. That is in fact exactly what happens, except for this: Over time the object happens to find itself closer and closer to the top of the page. When it has reached the top, it still continues to float up and down, but the y offset stops increasing. What do you make of this strange behaviour?

Sounds like a rounding error to me. You can fix it by adding a statement at the start of the function that automatically moves #bird to the correct starting position -- imperceptibly, one would hope.

EDIT: Complete rewrite because most of my previous answer was incorrect.
As per mblase's suggestion, you can use jQuery's offset() function to store the original offset, and then again to reset the element either in the callback of the second animation or at the start of floatUpAndDown().
var bird = $("#bird");
var birdOrgOffset = bird.offset();
function floatUpAndDown() {
bird.offset(birdOrgOffset);
bird.animate({top: '+=30px'}, 1400)
.animate({top: '-=30px'}, 1400, 'swing', floatUpAndDown);
}
floatUpAndDown();
And just a little tip for you (or anyone else). Making calls to $()—as in $("#bird")—is kind of expensive in terms of resources. If you find that you are repeatedly invoking the same selector, it's often better to call it once and then store the result in a var as I have done in the example above. So rather than reselecting #bird every time we restart the animation an reset the offset, we do it once and reuse the result after that. In general, you probably won't notice...but if you're using selectors in a loop then it will definitely start to add up.

Related

Update 'duration' of .animate function within the 'onChange'?

I'm trying to improve an animation that I've been working on where things move across the screen.
Currently the object moves at a set speed and has no variance.
I'm trying to include two features that will ultimately end up doing the same thing; changing the speed of the animated object.
I'd like the user to be able to change the speed and also for the object to slow down or speed up depending on where it is on the screen.
I'm not sure if I'm looking in the right place as currently I've been unable to update the duration once the animation loop has started. I first thought I could replace the number with a function that would return an int. This works in that the value of 'speed' changes but the animate loop is not updated.
Any help is hugely appreciated, thanks.
Code snippets below.
function moveObj () {
//initially the duration was set here. I understand that will not work as the animation is only
//being called once.
//animation process
obj.animate('top', '+=' + canvas.height, {
duration: speedOfObj(0),
abort: function () {
},//end abort callback
onChange: function () {
//testing only//
speedOfObj(1000);
}
//test function to see what the results would be. speed changes when called within the on change but the animation is not affected.
function speedOfObj(modifier){
var speed = 10000 / (new Number(speedControl.value));
if(modifier == 0){
console.log("speed: "+speed);
return speed;
}else{
speed *= modifier;
console.log("speedBBBB: "+speed);
return speed;
}
}
Once a jQuery animation is off and running, it's pretty much off on its own. If you want to change how it works, you can .stop(true) it and then start up a new animation that starts again from where it is now at your new speed.
It's also possible to implement a custom step function in the animation that might takes some queues for how to work from outside influences that can change during the animation, but I think that would end being much more complicated than just stopping the original animation and starting a new one that moves at the newly desired speed.
Working demo: http://jsfiddle.net/jfriend00/tzxca/

Waiting for animations in a SetInterval gameloop

So my problem is I want to insert a custom animation but I don't want to ruin my gameloop.
My initial gameloop is stated here:
function init(){
if(!gameOver){
if(resetInterval>-1) clearInterval(resetInterval);
createBlock();
resetInterval = setInterval(moveDownCheck,gameSpeed);
}
}
My game is a tetris like game except instead of dropping tetriminos, I drop 2x1 blocks of different color. The moveDownCheck method checks if there are any blocks under my 2x1 block and then drops it by 1 row. This works fine until I have a block hanging without a block underneath since the 2x1 blocks are connected. I want to insert a drop animation that would take about a second and drop the hanging block by the same gameSpeed increment.
Here is my attempt that doesn't work:
function moveFallingDown(){
fbDownFlag = false;
clearInterval(resetInterval);
fbInterval = setInterval(function(){
fallingBlock.row++;
console.log("Dropped One Row");
},gameSpeed);
while(landscape[fallingBlock.row+1][fallingBlock.col]==0){
console.log("Waiting to Drop Falling Block");
}
clearInterval(fbInterval);
resetInterval = setInterval(moveDownCheck,gameSpeed);
}
Here I am attempting to wait for the function(){fallingBlock.row++;}, but my game just crashes and in the console "Dropped One Row" yet "Waiting to Drop Falling Block" will display thousands of times.
I guess I shouldn't be using a while loop here, but the only other solution I can think of would be a complete rework of my design, or nested setInterval methods which would just make my head hurt too much.
You can't do this with a while loop, you need to use a recursive function. window.setTimeout would work, however this seems like a good use case for requestAnimationFrame. Check it out here: https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame
You can use that to call your moveFallingDown method, and check how long has passed since the last animation frame to move your animation the right amount according to the game speed, by using the high precision timestamp passed to the requestAnimationFrame callback.
#Adrien Delessert's advice is correct but I'll just add that you're definitely confusing setTimeout and setInterval.
First of all you don't need a while loop.
setInterval IS a loop. So, if you wanted to use it you'd need to wrap (basically) the whole game in a method that moves the game forward (whatever that means) and pass that to setInterval.
However, what you're doing (and this is actually not a terrible approach) is to use it as a means to animate specific things. In that case you should have a recursive(ish) function that keeps calling setTimeout when it's done, if the conditions for another round of animation are met.
I haven't ever used requestAnimationFrame, but that does sound like a much more elegant approach to the problem.
The reason it's better is that it leverages the browser's own refresh timer (about 60 times per second) and will slot your animation frames in along with its own refresh.
So yes, you will listen for that callback and then react to it as necessary. If 60x per second is too fast, you'll need to put in a % based counter for how many of those frames you wish to actually react to.

How to force a reflow in javascript in webkit 1.2.5

I've tried everything that is supposed to invoke a reflow but that isn't happening. I'm calling my test function 10 times to draw some element on my screen and I move that element through each iteration. That loop is executed immediately and in the end I get one picture instead of seeing the movement of the element on the screen.
It's as if when all work is done, reflow and drawing on the screen is invoked. But i want to see each drawing.
All the things I've tried didn't give any results. The only thing that works is alert(), but i don't need an interaction with user.
I'm using an webkit 1.2.5 if that helps.
If I'm not understandable enough I will try to explain better.
This the code I'm forcing to reflow
var i = 0;
for(;i<500;i+=50){
fTestInfo(i);
console.log("Test loop!!! "+i);
}
The thing I nedd is to see a picture on my screen each time fTestInfo(i) is executed but instead, i only see the ending result.
fTestInfo depends on i it moves in left by the value of i.
I see you are using a for loop which typically means you misunderstand how timers work. The for loop is synchronously executed and you are probably setting all the timers at once.
Try this:
(function loop(i) {
if (i >= 500) {
return;
}
document.querySelector("div").style.left = i + "px";
setTimeout(function() {
loop(i + 1);
}, 16);
})(0);
​
demo http://jsfiddle.net/UCfmF/
I suppose you mean getting a value like .offsetWidth? This is not guaranteed to make a visible reflow on the screen, browsers may wait for some time (read: until javascript execution stops) before actually attempting to paint anything on the screen even if you are doing actions that trigger reflows.
This means that if you append 1000 elements to the document, it will not trigger 1000 reflows. Even if you fetch .offsetWidth in between each iteration. It will just be calculated for you but not necessarily painted.
You need to move the elements with a timer as the end of javascript execution is when browsers flush out any queued reflows.
See http://dev.opera.com/articles/view/efficient-javascript/?page=3#reflow
As stated earlier, the browser may cache several changes for you, and
reflow only once when those changes have all been made. However, note
that taking measurements of the element will force it to reflow, so
that the measurements will be correct. The changes may or may not not
be visibly repainted, but the reflow itself still has to happen behind
the scenes.
You need to give the browser the opportunity to enter its event loop between each iteration.
Use setTimeout to schedule each iteration of the drawing:
function scheduledTestInfo(i) {
setTimeout(function() {
fTestInfo(i);
}, i); // calls each function 50ms apart
}
var i = 0;
for ( ; i < 500 ; i += 50) {
scheduledTestInfo(i);
}

jQuery animate not moving to the correct location

I've run into a mysterious problem. I'm using jQuery's animate function and the object I'm animating is not moving to the 'left' coordinate I provide; rather the left coordinate is always 0. However, the 'top' coordinate does move as expected. I had this working fine at one point and I rearranged some things around and it has stopped.
var new_x,
new_y,
player_tranistion_holder;
new_x = bg_app.boardModel[id_num].x+'px';
new_y = (bg_app.boardModel[id_num].chips.length) * bg_app.game_utils.parse_css_dimension(selected_chip.chip.css('height'));
console.log(new_x) //outputs 300px for example - the expected value;
selected_chip.chip.animate({'left': new_x, 'top': new_y }, 500, 'easeInOutQuad', function () {
console.log(selected_chip.chip.css('left'); //output is 0
});
Any idea what could cause this to screw up? The stuff I changed around it was merely in how the function came to be called, nothing in the function itself. Do I have some syntax error that I'm missing?
I've put the broken site here:
http://www.warunicorn.com/bg_show_busted/
here is an example with it working:
http://www.warunicorn.com/bg_show/
Any advice would be appreciated.
I found the problem. When using the animate function if the new value passed in is the same as the current value of the object jQuery gets confused and sets the value to 0. I haven't done extensive testing on this and so there may be exceptions where it does work, but in this case, that was the problem.

Issue with a javascript jQuery script in OpenX

I'm using OpenX at work, and one of my boss requirements is a expandable banner. For that (and made a horrible simplification of the whole story) I made this script.
function retro(){
var acs = jQuery('#trial_center').height() - 5;
jQuery('#trial_center').css('height', acs + 'px');
}
jQuery(document).ready(function(){
jQuery("#trial_center").mouseover(function(){
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
})
jQuery("#trial_center").mouseleave(function(){
var c = 89;
while (c > 0) {
setTimeout("retro()", 1000);
c--;
}
})
});
The problem I have is in the mouseleave event: the original idea was to made this loop several times (89 times), and each time, decrease the height of the banner until it get his original size. Why this? Because my boss want an "effect", and this effect must be in sync with the customer's flash.
The problem is that instead of decrease his size progressively, apparently the script made all the operations an "after" the sum of setTimeout calls, updated the page. So, the result is exactly as the banner shrinks one time from the expanded size to the original size.
I don't know what is wrong with this, or if exists other more intelligent solution.
Any help will be very appreciate.
Thanks in advance
Your loop setting the timeout is just setting 89 timers for one second later than the loop runs, and the loop will run in milliseconds — so they'll all fire about a second later. That doesn't sound like what you want to do.
Two options for you:
1. Use animate
jQuery's animate function seems like it does what you want. You can tell jQuery to animate the size change, and you tell it how long to take to do so:
jQuery('#trial_center').animate({
height: "500px" // Or whatever the desired ending height is
}, 1000);
That will animate changing the height of the container from whatever it is at the point that code runs to 500px, across the course of 1,000 milliseconds (one second). Obviously you can change the duration to whatever you like.
2. Set up the timer loop manually
If for whatever reason you don't want to use animate, you can do this manually (of course you can; jQuery can't do anything you can't do yourself, it just makes things easier). Here's how to set up a timer loop:
jQuery("#trial_center").mouseleave(function(){
var c = 89;
// Do the first one right now, which will schedule the next
iteration();
// Our function here lives on until all the iterations are
// complete
function iteration() {
// Do one
retro();
// Schedule this next unless we're done
if (--c > 0 {
setTimeout(iteration, 100); // 100ms = 1/10th second
}
}
});
That works because iteration is a closure over c (amongst other things). Don't worry about the term "closure" if it's unfamiliar, closures are not complicated.
Separately: You're using mouseover to set the height of the trial_center element a second later; you probably wanted mouseneter rather than mouseover. mouseover repeats as the mouse moves across it.
Off-topic:
It's best not to use strings with setTimeout; just pass it a function reference instead. For example, instead of
setTimeout("retro()", 1000);
you'd use
setTimeout(retro, 1000); // No quotes, and no ()
And for the other place you're using, instead of
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
you'd use
setTimeout(function() {
jQuery('#trial_center').css('height', '500px');
}, 1000);

Categories

Resources