I have written a jQuery function which accepts an elementId and an integer. The function then generates a counter which increments at the average speed of searches made (per month) since you initialised the function.

  • The elementId is where the counter is placed on the page.
  • The integer is the number of monthly searches manually taken from Google adWords.

There are a number of factors I'd like to improve; firstly, the error handling as I am trying to get a reasonably accurate counter Math.round shouldn't be used. But other functions to handle divide by zero issues etc are fine.

I'd also like to improve how the calculations are performed to establish the interval speed, it is currently written to show relatively clear calculation steps (I'd like to think).

(function ($) {

/* Initialise Counters*/
keywordSearchCounter(9292208, 'footballCounter');
keywordSearchCounter(292410, 'tennisCounter');
keywordSearchCounter(779180, 'chessCounter');

function keywordSearchCounter(searchesPerMonth, elementId) {

    if($.type(searchesPerMonth) !== "number") return;

    if(searchesPerMonth === 0) return;

    console.log("function initialised for '" + elementId.toString() + "'");

    /* Do some math on searches */
    var daysInMonth = 28,
        hoursInDay = 24,
        minutesInHour = 60,
        secondsInMinute = 60;

    var searchesPerDay = searchesPerMonth / daysInMonth;
    var searches = searchesPerDay;
    console.log('per day: ' + searches);

    var searchesPerHour = searchesPerDay / hoursInDay;
    searches = searchesPerHour;
    console.log('per hour: ' + searches);

    var searchesPerMinute = searchesPerHour / minutesInHour;
    searches = searchesPerMinute;
    console.log('per minute: ' + searches);

    var searchesPerSecond = searchesPerMinute / secondsInMinute;
    searches = searchesPerSecond;
    console.log('per second: ' + searches);

    var interval = (1 / searches) * 1000;
    var counter = 0;
    console.log('interval set to: ' + interval);

    /* Set the counter speed */
    setInterval(function () {
        counter += 1;
        document.getElementById(elementId).innerHTML = counter;
    }, interval);

A have provided a jsFiddle to show a working example: Demo

Any advice, even on the instantiation of my IIFE would be most welcome!


I'm going to suggest something completely different to your current implementation... I'll dump the code first (see js fiddle) notice that the fiddle has changed from running the script 'onload' to 'no wrap - in <body>'

// Wrap it all in an IIFE if you want.
var SECONDS_IN_A_MONTH = 60*60*24*30; //seconds * minutes * hours * days

// Search for elements to be turned into counters
$('.search-counter').each(function() {
    var $this, searches, interval, counter;
    $this = $(this);
    searches = parseInt($this.data('searches'), 10);

    if (searches < 1  || isNaN(searches)) return;

    interval = (SECONDS_IN_A_MONTH/searches) 
    counter = 0;

    // Start the counter at zero.

    setInterval(function () {
    }, interval);

I've modified your html to include the number of searches per month in a data attribute. I've also added a class to the elements that will be turned into counters. This allows the javascript to deal with any number of counters on a page. E.g. a counter would look like:

<h6> "Chess" has had... </h6> 
<p><span class="label label-warning search-counter" data-searches="779180"></span>
<span class="label label-info">searches</span>

I don't think you gain anything by splitting up the calculation of the interval. If you think it about another way, you're trying to calculate the time between searches which is clearly just the number of seconds in a month divided by the number of times the search was executed.

If you can't change the html, or would rather keep your code then I have a few points.

  • It's nicer to start the counter at zero.
  • It's better not to rely on hoisting - put simply, only use a function after you've written it.
  • document.getElementById(elementId).innerHTML = counter; will have to search the DOM on every 'tick'. Cache the element
  • I used 30 for days in a month rather than 28 - use whichever period the data is from.

All pretty minor stuff really.

| improve this answer | |
  • \$\begingroup\$ Beautiful - I really like the use of .each here, splendid re-factoring this is much more efficient! Also, thank you for the summary comments these were most helpful. \$\endgroup\$ – Matt D. Webb Nov 28 '14 at 20:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.