views:

152

answers:

4

I've got an element on my page that I would like to increment from time to time:

<p class="count">28</p>

Within my javascript, I have an interval that runs once every 5 seconds and asynchronously grabs the new (higher) number from the server. This functions somewhat along the lines of a counter.

setInterval(function(){
  $.post("getcount.php", function(num){
    latestNum = num;
  });
}, 5000);

If the asynchronous request pulls down the number 56, I would like to see the paragraph text change from 28 to 56, showing many (or most) or the intermediates while it increments. What I'm currently doing is setting another interval to constantly check the paragraph text, and the local variable. If the paragraph text is lower than the variable value, it increments it by 1. It does this every half-a-second.

setInterval(function(){
  currNum = Number( $(".count").text() );
  if (currNum < latestNum)
    $(".count").text( currNum + 1 );
}, 50);

My question is this: Is there a better way to do this without having an interval constantly running? Is this animated-incrementing something I can invoke from the response of an asynchronous request, and then have it cease once the two numbers have met in value? I should note also that there exists the possibility that the next request will take place before the two numbers have been reconciled in value.

How would the collective you do this?

+2  A: 

Corrected answer:

To the commenters: Have realized that myself ... still thank you!

var interval = setInterval(function(){
  currNum = Number( $(".count").text() );
  if (currNum < latestNum) {
    $(".count").text( currNum + 1 );
  } else {
    setTimeOut(function() { clearInterval(interval) }, 0);
  }
}, 50);

This saves the interval in a variable and then calls clearInterval to stop the interval. setTimeOut is needed as otherwise the interval would be cleared while executing it.

First Answer:

Why not combinding the two functions, i.e.:

setInterval(function(){
  $.post("getcount.php", function(num){
    latestNum = num;
    if (currNum < latestNum) {
        currNum = latestNum;
        $(".count").text( currNum );
    }
  });
}, 5000);

This would prevent the update every half second.

Obalix
Yeah but then you loose the animation of the number updating...
marcgg
I want to animate through the intermediates. This approach would simply replace the current value with the new value.
Jonathan Sampson
that's cool. Have you tested it?
marcgg
@marcgg: Yes, I have.
Obalix
cool, +1 to you then ^^
marcgg
+1. With a few modifications I'd use it myself. ;)
Joel Potter
A: 

I'd spawn the second interval when the result was received and terminate it when the target value was reached.

Lazarus
+1  A: 

Building on Obalix answer, I would recommend having the 5 second interval trigger the smaller interval.

var interval = null, currNum = null;
setInterval(function(){
  $.post("getcount.php", function(num){
    latestNum = num;
    if (currNum === null)
        currNum = Number( $(".count").text() ); // should only execute once
    if (currNum < latestNum && interval === null)
        interval = setInterval(ObalixFunction, 50);
  });
}, 5000);

and modify

function ObalixFunction() {
    if (currNum < latestNum) {
        // increment the currNum variable too so we don't have to keep parsing it
        $(".count").text( ++currNum ); 
    } else {
        setTimeOut(function() { clearInterval(interval); interval = null; }, 0);
    }
}
Joel Potter
+1  A: 

Here's my answer - actually two answers.
Stepping linearly towards the target value with a fixed interval risks not ever getting there.
So I have provided another solution too which halves the difference each time, gets there much quicker even for large differences.
Both examples can also count down.

$( function() {

    var tRef,
        counter = $( '#counter' );
        target  = $( '#target' );

    /*
     * A function that eases towards its target
     */
    function convergeEasing( target ) {
        clearTimeout( tRef );
        target = parseInt( target );
        var current = parseInt( counter.html() );
        var diff = target - current;
        if( diff ) {
            var rounder = diff > 0 ? Math.ceil : Math.floor;
            current += rounder( diff/2 );
            counter.html( current );
            tRef = setTimeout( function() {
                convergeEasing( target );
            }, 250 );
        }
    }

    /*
     * A function that plods towards its target
     */
    function convergeLinear( target ) {
        clearTimeout( tRef );
        target = parseInt( target );
        var current = parseInt( counter.html() );
        var diff = target - current;
        if( diff ) {
            current += diff > 0 ? 1 : -1;
            counter.html( current );
            tRef = setTimeout( function() {
                convergeLinear( target );
            }, 250 );
        }
    }

    /*
     * I've mocked your ajax request up here for demo purposes
     * using the linear way as per your question
     */
    setInterval( function(){
        var n = Math.round( Math.random()*1000 );
        target.html( n );
        convergeLinear( n );
    }, 5000 );

});

<div id="target">20</div>
<div id="counter">20</div>

I suppose the easing strategy could be function that is passed in rather than the two functions duplicating much of their code

meouw