How to create an accurate timer in javascript?

Why is it not accurate?

Because you are using setTimeout() or setInterval(). They cannot be trusted, there are no accuracy guarantees for them. They are allowed to lag arbitrarily, and they do not keep a constant pace but tend to drift (as you have observed).

How can I create an accurate timer?

Use the Date object instead to get the (millisecond-)accurate, current time. Then base your logic on the current time value, instead of counting how often your callback has been executed.

For a simple timer or clock, keep track of the time difference explicitly:

var start = Date.now();
setInterval(function() {
    var delta = Date.now() - start; // milliseconds elapsed since start
    …
    output(Math.floor(delta / 1000)); // in seconds
    // alternatively just show wall clock time:
    output(new Date().toUTCString());
}, 1000); // update about every second

Now, that has the problem of possibly jumping values. When the interval lags a bit and executes your callback after 990, 1993, 2996, 3999, 5002 milliseconds, you will see the second count 0, 1, 2, 3, 5 (!). So it would be advisable to update more often, like about every 100ms, to avoid such jumps.

However, sometimes you really need a steady interval executing your callbacks without drifting. This requires a bit more advanced strategy (and code), though it pays out well (and registers less timeouts). Those are known as self-adjusting timers. Here the exact delay for each of the repeated timeouts is adapted to the actually elapsed time, compared to the expected intervals:

var interval = 1000; // ms
var expected = Date.now() + interval;
setTimeout(step, interval);
function step() {
    var dt = Date.now() - expected; // the drift (positive for overshooting)
    if (dt > interval) {
        // something really bad happened. Maybe the browser (tab) was inactive?
        // possibly special handling to avoid futile "catch up" run
    }
    … // do what is to be done

    expected += interval;
    setTimeout(step, Math.max(0, interval - dt)); // take into account drift
}

I'ma just build on Bergi's answer (specifically the second part) a little bit because I really liked the way it was done, but I want the option to stop the timer once it starts (like clearInterval() almost). Sooo... I've wrapped it up into a constructor function so we can do 'objecty' things with it.

1. Constructor

Alright, so you copy/paste that...

/**
 * Self-adjusting interval to account for drifting
 * 
 * @param {function} workFunc  Callback containing the work to be done
 *                             for each interval
 * @param {int}      interval  Interval speed (in milliseconds)
 * @param {function} errorFunc (Optional) Callback to run if the drift
 *                             exceeds interval
 */
function AdjustingInterval(workFunc, interval, errorFunc) {
    var that = this;
    var expected, timeout;
    this.interval = interval;

    this.start = function() {
        expected = Date.now() + this.interval;
        timeout = setTimeout(step, this.interval);
    }

    this.stop = function() {
        clearTimeout(timeout);
    }

    function step() {
        var drift = Date.now() - expected;
        if (drift > that.interval) {
            // You could have some default stuff here too...
            if (errorFunc) errorFunc();
        }
        workFunc();
        expected += that.interval;
        timeout = setTimeout(step, Math.max(0, that.interval-drift));
    }
}

2. Instantiate

Tell it what to do and all that...

// For testing purposes, we'll just increment
// this and send it out to the console.
var justSomeNumber = 0;

// Define the work to be done
var doWork = function() {
    console.log(++justSomeNumber);
};

// Define what to do if something goes wrong
var doError = function() {
    console.warn('The drift exceeded the interval.');
};

// (The third argument is optional)
var ticker = new AdjustingInterval(doWork, 1000, doError);

3. Then do... stuff

// You can start or stop your timer at will
ticker.start();
ticker.stop();

// You can also change the interval while it's in progress
ticker.interval = 99;

I mean, it works for me anyway. If there's a better way, lemme know.