Iamsamstimpson
Iamsamstimpson

Reputation: 1357

Javascript Incrementing Numbers

What I'm after:

There are 5 counters on my page, they should all start from 0 and count upwards (increment) at the same time to different values that are set at different speeds. So for example I set 10, 25, 1500, 400, 500 in my variables I would like them all to reach these values.

• start at the same time • all increment at different speeds • stop at value set for each

My Problem

I can't figure out a good way to write some simple very efficient javascript to do this (going to be used on a mobile), currently I'm using setinterval which seems to start all the counters at different times and jerks/not smooth..

how far I've got so far http://jsfiddle.net/95smH/

setInterval(f1,500);
setInterval(f2,1500);
setInterval(f3,500);
setInterval(f4,50);

var v1      = 0
,   v2 = 0
,   v3  = 0
,   v4  = 0;

function f1() {
    $('.count.v1').text(v1);
    if ( v1 < 450 )
    {
        v1 ++;
    }
}
function f2() {
    $('.count.v2').text(v2);
    if ( v2 < 50 )
    {
        v2 ++;  
    }
}
function f3() {
    $('.count.v3').text(v3);
    if ( v3 < 23 )
    {
        v3 ++;  
    }
 }
function f4() {
    $('.count.v4').text(v4);
    if ( v4 < 4 )
   {
        v4 ++;  
    }
}

Upvotes: 1

Views: 895

Answers (4)

GameAlchemist
GameAlchemist

Reputation: 19294

Here is a solution quite cpu-friendly :
- you only have one setInterval using a base period that you decide.
- On each interval you iterate through your all counters doing very little work each time. (i even avoided to use % ).
- The elements are cached.

jsbin is here : http://jsbin.com/urUxojo/2/edit?html,js,console,output

window.onload=function() {

// this is the period of the base Interval in ms. 
// The counters will be aligned on this base period
Counter.basePeriod = 50 ;

// constructor of a Counter
// ! initializes the counter display.
function Counter(id, period_ms, max, initialValue) {
         this.element  = document.getElementById(id);
         this.period   = Math.ceil(period_ms / Counter.basePeriod) || 1;
         this.max      = max ;
         this.value    = initialValue || 0 ;
         this.last     = 0;
         this.element.value = (this.value);
         return this;
};

// called on each basePeriod tick
// updates the element every period until it reaches its max value.
Counter.prototype.tick = function() {
        this.last++;
        if (this.last == this.period  && 
                           this.value != this.max ) {
             this.value++;
             this.element.value = (this.value);
             this.last=0;
        } 
};

Counter.counters = []; 

Counter.start= function () {   
       function handleCounters() {
         for (var i=0; i< Counter.counters.length; i++) {  Counter.counters[i].tick();  }
       }
       Counter._interval = setInterval(handleCounters, Counter.basePeriod); 
};

Counter.add = function() {
    var newCounter = Object.create(Counter.prototype);
    Counter.counters.push( Counter.apply(newCounter, arguments) );
}

// -------------------------------

Counter.add('cnt1'      , 500  , 450 );
Counter.add('cnt2'      , 400  , 40 );
Counter.add('topCounter', 1000 , 5  );

Counter.start();

}

Upvotes: 1

kajacx
kajacx

Reputation: 12939

Well, you always want to get rid of duplicity, so you can do:

function IntervalClass(selector, sleepTime, maxValue, startValue) {
    var curValue = startValue;

    this.tick = function () {
        if (curValue < maxValue) {
            curValue++;
        }
        $(selector).html(curValue);
    };

    this.start = function () {
        setInterval(this.tick, sleepTime);
    };
}

new IntervalClass(".count.users", 1000, 35, 0).start();
new IntervalClass(".count.awardshows", 500, 20, 0).start();
new IntervalClass(".count.countries", 1500, 15, 0).start();
new IntervalClass(".count.regions", 200, 100, 0).start();

JSFIDDLE

Upvotes: 0

Ingo B&#252;rk
Ingo B&#252;rk

Reputation: 20033

This would be my shot at a slightly more general way. It will also kill the setInterval once every counter has finished. Of course this will still have the typical inaccuracy of Javascript intervals.

var counter = new (function () {
    'use strict';

    var counters = [],
        handler = null,
        precision = 50;

    this.addCounter = function (maxValue, interval, selector) {
        counters.push({
            maxValue: maxValue,
            currentValue: 0,
            interval: interval / precision,
            intervalCounter: 0,
            $output: $(selector)
        });

        return this;
    };

    this.start = function () {
        handler = setInterval(function () {
            var stillRunning = false;
            for (var i = 0; i < counters.length; i++) {
                var counter = counters[i];

                counter.intervalCounter++;
                if (counter.intervalCounter === counter.interval) {
                    counter.intervalCounter = 0;
                    counter.currentValue++;

                    if (counter.currentValue <= counter.maxValue) {
                        stillRunning = true;
                        counter.$output.val(counter.currentValue);
                    }
                } else {
                    stillRunning = true;
                }
            }

            if (!stillRunning) {
                clearInterval(handler);
            }
        }, precision);
    };

    return this;
})();

counter
    .addCounter(50, 250, '.v1')
    .addCounter(25, 500, '.v2')
    .start();

Demo fiddle: http://jsfiddle.net/s9AYz/1/ Note that precision must be set such that the interval of each counter is a multiple of it (Boris solution has the same requirement).

Upvotes: 1

Boris B.
Boris B.

Reputation: 5024

This is based on your own code style:

setInterval(tick,50);

var cusers      = 0
,   cawardshows = 0
,   ccountries  = 0
,   cregions    = 0;

var susers = 500
,   sawardshows = 1500
,   scountries = 500
,   sregions = 50;

var i = 0;

function tick() {    
    $('.count.users').text(cusers);
    if ( cusers < 450 && i % susers == 0)
    {
        cusers ++;
    }

    $('.count.awardshows').text(cawardshows);
    if ( cawardshows < 50 && i % sawardshows == 0 )
    {
        cawardshows ++; 
    }
    $('.count.countries').text(ccountries);
    if ( ccountries < 23  && i % scountries == 0 )
    {
        ccountries ++;  
    }
    $('.count.regions').text(cregions);
    if ( cregions < 4 && i % sregions == 0)
    {
        cregions ++;    
    }
    i += 50;
}

The idea is to have only one timer, so all updates will be synchronized to the same clock. Since values have different "speeds", you update some of them only every nth step.

Upvotes: 1

Related Questions