Syncing code with the sun
setInterval()
and requestAnimationFrame()
both let you call some piece of Javascript on a regular interval.
In both, the elapsed time is relative to when your script first starts executing.
Let's look at how you can keep your code in sync with a wall clock instead.
Javascript
(function (exports, $) {
var tickPeriodMs = Number(5 + "e3"); // sec/step, must be >= 1!
var frameStart, frameEnd; // absolute (wall clock) frame start / end
exports.setTickPeriod = function (periodInSeconds) {
tickPeriodMs = Number(periodInSeconds + "e3");
};
function onFrame() {
frameStart = Date.now();
if (!frameEnd) frameEnd = Date.now();
// does multiple exist in range [frameEnd, frameStart]?
var nearest;
var rem = frameEnd % tickPeriodMs;
if (rem == 0) nearest = frameEnd;
else nearest = frameEnd + tickPeriodMs - rem;
var isTick = frameEnd <= nearest && nearest <= frameStart;
if (isTick) {
console.log(
"Added " + Number(tickPeriodMs + "e-3") + "+ second(s) to local time"
);
$(window).trigger("tick"); // or similar
}
frameEnd = Date.now();
requestAnimationFrame(onFrame);
}
requestAnimationFrame(onFrame);
})((window.Wallclock = {}), jQuery);
Include this code on your page outside of any onReady
(or similar) callbacks.
The first and last lines set up our wrapper object, window.Wallclock
.
A single function, Wallclock.setTickPeriod()
, sets how often an event named tick
is emitted from the window object.
For example, if you want to log a message at the start of each minute (each time your watch's second hand reaches 12):
Javascript
// after including the code above
Wallclock.setTickPeriod(60);
$(window).on("tick", function () {
console.log("Hello! Current time := " + new Date());
});
Here's how it works.
I drive a main loop via requestAnimationFrame()
.
Each time through, I record the Unix timestamps of the end of the last frame, and the start of the current frame.
The Unix timestamp of any wall clock event - like "at 12:30" or "15 minutes past the hour" - occurs at a multiple of the milliseconds in that interval. For instance, suppose the current (Unix) time is 1467844308776 (that's 22:31:48 GMT on July 6, 2016). You'll reach 22:35:00 at 1467844500000. That last number is a multiple of 300,000 (the number of milliseconds in 5 minutes).
Based on the value you provide to .setTickPeriod()
, the code determines if a multiple of that period has occurred between frames.
If it has, we emit a tick
, knowing that the wall clock time has crossed over the boundary we're interested in.
A final note about accuracy:
This code will potentially miss some tick
's if your browser window is minimized, or the current tab changed.
This is due to the battery-friendly nature of requestAnimationFrame
, which browsers run at a lower rate (or not at all!) under those conditions.
Consider a tick to mean, "at least N seconds has been added to the local time".