A precise browser-based timer that uses requestAnimationFrame for accurate scheduling. Supports multiple independent tick cycles, playback speed control, and seeded pseudo-random number generation per tick.
npm install precise-browser-timer
# or
bun add precise-browser-timerimport { PreciseBrowserTimer } from "precise-browser-timer";
const timer = new PreciseBrowserTimer({
tick_cycle_list: [
{
tick_unit_ms: 100, // fire every 100ms
on_tick: (tick, prand_numbers, elapsed_ms) => {
console.log(`Tick ${tick} at ${elapsed_ms}ms`);
},
},
],
});
timer.start();
// pause and resume
timer.pause();
timer.start();
// permanently stop
timer.stop();new PreciseBrowserTimer({
seed?: number; // RNG seed for deterministic random numbers (default: 0)
playback_speed?: number; // 0 < speed <= 1, values below 1 slow down playback (default: 1)
tick_cycle_list: PreciseTimerTickCycle[];
on_before_start?: (timestamp_ms: number) => void;
});Each entry in tick_cycle_list runs independently:
{
tick_unit_ms: number; // interval between ticks (min 5ms)
on_tick: (
tick: number, // current tick counter
prand_numbers: number[], // per-tick random numbers (see prand_range_list)
elapsed_ms: number, // total elapsed time since timer start
ticked_at_ts_ms: number // absolute timestamp of this tick
) => void;
on_complete?: (
last_tick: number,
elapsed_ms: number,
completed_at_ts_ms: number
) => void;
do_until_value?: number; // stop condition value
do_until_unit?: 'ms' | 'tick'; // interpret do_until_value as ms or tick count
loop?: boolean; // restart the cycle instead of completing
prand_range_list?: Array<{ from: number; to: number }>;
// generates one random integer per range, seeded deterministically per tick
}new PreciseBrowserTimer({
tick_cycle_list: [
{
tick_unit_ms: 16,
do_until_value: 5000,
do_until_unit: "ms",
on_tick: (tick) => render(tick),
on_complete: () => console.log("done"),
},
],
}).start();new PreciseBrowserTimer({
tick_cycle_list: [
{
tick_unit_ms: 100,
on_tick: (tick) => updateUI(tick),
},
{
tick_unit_ms: 1000,
on_tick: (tick) => saveProgress(tick),
},
],
}).start();new PreciseBrowserTimer({
seed: 42,
tick_cycle_list: [
{
tick_unit_ms: 200,
prand_range_list: [
{ from: 0, to: 100 }, // first number: 0–100
{ from: -10, to: 10 }, // second number: -10–10
],
on_tick: (tick, [x, y]) => {
moveSprite(x, y);
},
},
],
}).start();const timer = new PreciseBrowserTimer({
playback_speed: 0.5, // half speed
tick_cycle_list: [...],
}).start();
// change speed at runtime
timer.changePlaybackSpeed(0.25);const cycle = { tick_unit_ms: 100, on_tick: render };
const timer = new PreciseBrowserTimer({
tick_cycle_list: [cycle],
}).start();
// jump to tick 50
timer.scheduleTickChange(cycle, 50);
// or seek by timestamp
timer.scheduleTickChangeByTimestamp(cycle, 5000); // jump to 5s position| Method | Description |
|---|---|
start() |
Start the timer |
pause() |
Pause — tick state is preserved, start() resumes from where it left off |
stop() |
Permanently stop — cannot be restarted |
changePlaybackSpeed(speed) |
Change speed at runtime |
scheduleTickChange(cycle, tick) |
Jump to a specific tick on next frame |
scheduleTickChangeByTimestamp(cycle, ms) |
Jump to a tick derived from a timestamp |
MIT