views:

32

answers:

3

I recently asked a question about the same function, that solved my probelem and directed me to a tutoraial because I was using a while loop, this meant that my function did not animate, just freeze then resize. This new way, using setTimeout should work. The only thing is that it just snaps to the new size rather than animating to it. There are no errors according to firebug. Here is my current section of code that manages the animation.

// Resize in timeframe
// Work out distance
var widthdiff = width - parseInt(element.style.width);
var heightdiff = height - parseInt(element.style.height);

// Work out how miliseconds per step (100 in total)
var steptime = timeframe / 100;

// Work out how many pixels it needs to move each step
var widthpps = widthdiff / 100; // ERROR?
var heightpps = heightdiff / 100;

// Set up original sizes
var origwidth = parseInt(element.style.width);
var origheight = parseInt(element.style.height);

// Loop through all 100 steps setting a time out resize each time
var timers = [];
for(var i = 0; i < 100; i++)
{
    timers[i] = setTimeout(function() { // ERROR?
        element.style.width = origwidth + (widthpps * i) + 'px';
        element.style.height = origheight + (heightpps * i) + 'px';
    }, i * steptime);
}

The arguments are being passed fine, I have tested all that and I had it animating once, just wrong. So my problem will lie close to the comments called ERROR? I beleive. Thanks for any help.

+4  A: 

The problem is that the variable "i" will be shared by all the timeout functions.

You can write a separate function to build your timeout functions, or you can wrap the function in-line:

timers[i] = setTimeout((function(privateEye) {
    return function() { 
      element.style.width = origwidth + (widthpps * privateEye) + 'px';
      element.style.height = origheight + (heightpps * privateEye) + 'px';
    })(i), i * steptime);

When I say that "i" will be "shared", what I mean is that each of those functions you build in the loop will correctly refer to that loop variable. But what's happening to that variable? It's changing on each iteration of the loop. It's important to understand that the functions will reference the real "i" variable, not a frozen copy. By using a second function, as I did above, you make a copy of the "i" used in the loop.

Pointy
Got it! Thank you so much, this has been the most problamatic script I have ever written. Works perfect now though. I may even attempt easing which would produce some interesting errors I would imagine
Wolfy87
If you never had any coding problems, you'd never discover new insights into the way code works. Or, as Nietzche said, "That which does not kill us makes us stronger."
Pointy
Well if you really want it, the script can be found in that link. It is run by s.size('css selector of element', width, height, timeframe in milliseconds); http://github.com/Wolfy87/Spark/raw/master/spark.js
Wolfy87
+1  A: 

I haven't fully audited the code, but there's an error that matches your symptom here:

timers[i] = setTimeout(function() { // ERROR?
    element.style.width = origwidth + (widthpps * i) + 'px';    // <== error
    element.style.height = origheight + (heightpps * i) + 'px'; // <== error
}, i * steptime);

The problem is that the functions will receive a live reference to i, not a copy of its value as of that iteration of the loop. So they all see i as of its last value.

This is easily fixed:

timers[i] = setTimeout(makeStepFunction(i), i * steptime);

// Anywhere in the surrounding function, not in the loop:
function makeStepFunction(step) {
    return function() {
        element.style.width = origwidth + (widthpps * step) + 'px';
        element.style.height = origheight + (heightpps * step) + 'px';
    };
}

Now the function that setTimeout will call is using the argument passed into the makeStepFunction, rather than i.

You can do this without a named function, but it gets confusing really fast:

timers[i] = setTimeout((function(step) {
    return function() {
        element.style.width = origwidth + (widthpps * step) + 'px';
        element.style.height = origheight + (heightpps * step) + 'px';
    };
})(i), i * steptime);

This all has to do with how closures (functions that "close over" data) work in JavaScript. They're really powerful, but they're not complicated once you know how they work. FWIW, my blog post Closures are not complicated may be helpful. Once you know how they work, they lose their mystery (and become even more useful).

T.J. Crowder
Did you mean "they're complicated"?
Pointy
@Pointy: No. In fact, the link is to my blog post *"Closures are not complicated."* :-)
T.J. Crowder
Oh OK cool. I thought the comment was intended to support the "confusing" thing. I agree of course, and the main thing I wish is that the keyword "function" was a lot shorter :-)
Pointy
@Pointy: Re-reading it, I think it was pretty easy to misread, so I reworded it.
T.J. Crowder
+1  A: 

Another solution is to use setInterval and have the method use a global i that is updated on every pass and a cutoff value where setInterval i canceled.

var i = 0;
var interval = setInterval(
    function() {
        element.style.width = origwidth + (widthpps * i) + 'px';
        element.style.height = origheight + (heightpps * i) + 'px';
        i++;
        if(i==100)
             clearInterval(interval);  //Stops executing
    }, stepTime);

Found some explanation on how setTimeout and setInterval works to see which is best for your purpose. http://ejohn.org/blog/how-javascript-timers-work/

David Mårtensson