πŸš€ Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more β†’
Sign In

@armniko/ticker

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@armniko/ticker - npm Package Compare versions

Comparing version
2.1.1
to
2.2.0
+217
-1
dist/index.esm.js

@@ -1,1 +0,217 @@

var s=class{_minFps;_currentFps=60;_lastChecks=[];_calculateFpsEachMs=2e3;_msSinceLastCalculation=0;_lowFpsInRow=0;_lowFpsCallback;currentFps(){return this._currentFps}calculate(s){this.collectFps(s),this._msSinceLastCalculation+=s.deltaMs,this._msSinceLastCalculation>=this._calculateFpsEachMs&&(this._msSinceLastCalculation=0,this.calculateAverageFps(),this.checkAndNotifyLowFps())}onLowFps(s){this._lowFpsCallback=s}setMinFps(s){return this._minFps=s,this}collectFps(s){this._lastChecks.push(Math.round(1e3/s.deltaMs))}calculateAverageFps(){const s=this._lastChecks.reduce((s,e)=>s+e,0);this._currentFps=Math.round(s/this._lastChecks.length),this._lastChecks=[]}checkAndNotifyLowFps(){this._minFps&&this._currentFps<this._minFps&&(this._lowFpsInRow++,5===this._lowFpsInRow&&(this._lowFpsInRow=0,this._lowFpsCallback&&this._lowFpsCallback()))}},e=class{_fps=new s;_drawQueue=[];_logicQueue=[];_lowFpsQueue=[];_maxFps;_expectedFps;_time={delta:0,deltaMs:0};_lastTimestamp=0;_logicTicksAccumulatedMs=0;_requestFrameId;constructor(s){this.setFps({min:s?.minFps||0,max:s?.maxFps||60,expected:s?.expectedFps||60}),s?.onDrawTick&&this.addDrawTask(s.onDrawTick),s?.onLogicTick&&this.addLogicTask(s.onLogicTick),s?.onLowFps&&this.addLowFpsTask(s.onLowFps),this._fps.onLowFps(()=>{this._lowFpsQueue.forEach(s=>s.callback(this.fps()))})}start(){this._lastTimestamp=performance.now(),this.loop(this._lastTimestamp)}stop(){this._requestFrameId&&(window.cancelAnimationFrame(this._requestFrameId),this._requestFrameId=void 0)}isStarted(){return!!this._requestFrameId}setFps(s){return s.min&&s.min>0&&this._fps.setMinFps(s.min),s.max&&s.max>0&&(this._maxFps=this.compensatedMaxFps(s.max)),s.expected&&s.expected>0&&(this._expectedFps=s.expected),this}addDrawTask(s){return{index:this._drawQueue.push({callback:s})-1,type:"draw"}}addLogicTask(s){return{index:this._logicQueue.push({callback:s})-1,type:"logic"}}addLowFpsTask(s){return{index:this._lowFpsQueue.push({callback:s})-1,type:"low-fps"}}remove(s){switch(s.type){case"draw":this._drawQueue.splice(s.index,1);break;case"logic":this._logicQueue.splice(s.index,1);break;case"low-fps":this._lowFpsQueue.splice(s.index,1)}}fps(){return this._fps.currentFps()}msBetweenTicks(){return this._time.deltaMs}ticksMissed(){return this._time.deltaMs/(1e3/this._expectedFps)}loop(s){const e=1e3/this._maxFps,t=s-this._lastTimestamp;t>=e&&(this._time={delta:t/1e3,deltaMs:t},this._lastTimestamp=s,this._fps.calculate(this._time),this.runQueue()),this._requestFrameId=window.requestAnimationFrame(this.loop.bind(this))}runQueue(){this._logicQueue.length>0&&this.runLogicQueue(),this._drawQueue.length>0&&this.runDrawQueue()}runLogicQueue(){const s=1e3/this._expectedFps;this._logicTicksAccumulatedMs+=this._time.deltaMs;const e={delta:s/1e3,deltaMs:s};for(;this._logicTicksAccumulatedMs>=s;)this._logicQueue.forEach(s=>s.callback(e)),this._logicTicksAccumulatedMs-=s}runDrawQueue(){this._drawQueue.forEach(s=>s.callback(this._time))}compensatedMaxFps(s){return Math.floor(1.1*s)+10*Math.floor(s/100)}},t=class extends e{_isStarted=!1;start(){this._isStarted=!0}stop(){this._isStarted=!1}isStarted(){return this._isStarted}fps(){return 60}msBetweenTicks(){return 1e3/60}ticksMissed(){return 1}simulateLogicTick(s=void 0){this._logicQueue.forEach(e=>e.callback(s||{delta:1/this.fps(),deltaMs:1e3/this.fps()}))}simulateDrawTick(s=void 0){this._drawQueue.forEach(e=>e.callback(s||{delta:1/this.fps(),deltaMs:1e3/this.fps()}))}simulateLowFps(){this._lowFpsQueue.forEach(s=>s.callback(this.fps()))}};export{e as Ticker,t as TickerMock};
//#region src/fps/fps.ts
var Fps = class {
_minFps;
_currentFps = 60;
_lastChecks = [];
_calculateFpsEachMs = 2e3;
_msSinceLastCalculation = 0;
_lowFpsInRow = 0;
_lowFpsCallback;
currentFps() {
return this._currentFps;
}
calculate(time) {
this.collectFps(time);
this._msSinceLastCalculation += time.deltaMs;
if (this._msSinceLastCalculation >= this._calculateFpsEachMs) {
this._msSinceLastCalculation = 0;
this.calculateAverageFps();
this.checkAndNotifyLowFps();
}
}
onLowFps(callback) {
this._lowFpsCallback = callback;
}
setMinFps(minFps) {
this._minFps = minFps;
return this;
}
collectFps(time) {
this._lastChecks.push(Math.round(1e3 / time.deltaMs));
}
calculateAverageFps() {
const sumFps = this._lastChecks.reduce((a, b) => a + b, 0);
this._currentFps = Math.round(sumFps / this._lastChecks.length);
this._lastChecks = [];
}
checkAndNotifyLowFps() {
if (this._minFps && this._currentFps < this._minFps) {
this._lowFpsInRow++;
if (this._lowFpsInRow === 5) {
this._lowFpsInRow = 0;
if (this._lowFpsCallback) this._lowFpsCallback();
}
}
}
};
//#endregion
//#region src/ticker/ticker.ts
var Ticker = class {
_fps = new Fps();
_drawQueue = [];
_logicQueue = [];
_lowFpsQueue = [];
_maxFps;
_expectedFps;
_time = {
delta: 0,
deltaMs: 0
};
_lastTimestamp = 0;
_logicTicksAccumulatedMs = 0;
_requestFrameId;
constructor(options) {
this.setFps({
min: options?.minFps || 0,
max: options?.maxFps || 60,
expected: options?.expectedFps || 60
});
if (options?.onDrawTick) this.addDrawTask(options.onDrawTick);
if (options?.onLogicTick) this.addLogicTask(options.onLogicTick);
if (options?.onLowFps) this.addLowFpsTask(options.onLowFps);
this._fps.onLowFps(() => {
this._lowFpsQueue.forEach((item) => item.callback(this.fps()));
});
}
start() {
this._lastTimestamp = performance.now();
this.loop(this._lastTimestamp);
}
stop() {
if (this._requestFrameId) {
window.cancelAnimationFrame(this._requestFrameId);
this._requestFrameId = void 0;
}
}
isStarted() {
return !!this._requestFrameId;
}
setFps(options) {
if (options.min && options.min > 0) this._fps.setMinFps(options.min);
if (options.max && options.max > 0) this._maxFps = this.compensatedMaxFps(options.max);
if (options.expected && options.expected > 0) this._expectedFps = options.expected;
return this;
}
addDrawTask(callback) {
return {
index: this._drawQueue.push({ callback }) - 1,
type: "draw"
};
}
addLogicTask(callback) {
return {
index: this._logicQueue.push({ callback }) - 1,
type: "logic"
};
}
addLowFpsTask(callback) {
return {
index: this._lowFpsQueue.push({ callback }) - 1,
type: "low-fps"
};
}
remove(taskId) {
switch (taskId.type) {
case "draw":
this._drawQueue.splice(taskId.index, 1);
break;
case "logic":
this._logicQueue.splice(taskId.index, 1);
break;
case "low-fps":
this._lowFpsQueue.splice(taskId.index, 1);
break;
}
}
fps() {
return this._fps.currentFps();
}
/**
* @deprecated Use `time.deltaMs` provided in draw/logic task callbacks instead.
*/
msBetweenTicks() {
return this._time.deltaMs;
}
/**
* @deprecated Use `time.deltaMs` provided in draw/logic task callbacks instead.
*/
ticksMissed() {
return this._time.deltaMs / (1e3 / this._expectedFps);
}
loop(currentTimestamp) {
const maxDeltaMs = 1e3 / this._maxFps;
const deltaMs = currentTimestamp - this._lastTimestamp;
if (deltaMs >= maxDeltaMs) {
this._time = {
delta: deltaMs / 1e3,
deltaMs
};
this._lastTimestamp = currentTimestamp;
this._fps.calculate(this._time);
this.runQueue();
}
this._requestFrameId = window.requestAnimationFrame(this.loop.bind(this));
}
runQueue() {
if (this._logicQueue.length > 0) this.runLogicQueue();
if (this._drawQueue.length > 0) this.runDrawQueue();
}
runLogicQueue() {
const expectedDeltaMs = 1e3 / this._expectedFps;
this._logicTicksAccumulatedMs += this._time.deltaMs;
const time = {
delta: expectedDeltaMs / 1e3,
deltaMs: expectedDeltaMs
};
while (this._logicTicksAccumulatedMs >= expectedDeltaMs) {
this._logicQueue.forEach((item) => item.callback(time));
this._logicTicksAccumulatedMs -= expectedDeltaMs;
}
}
runDrawQueue() {
this._drawQueue.forEach((item) => item.callback(this._time));
}
compensatedMaxFps(maxFps) {
return Math.floor(maxFps * 1.1) + Math.floor(maxFps / 100) * 10;
}
};
//#endregion
//#region src/ticker/ticker-mock.ts
var TickerMock = class extends Ticker {
_isStarted = false;
start() {
this._isStarted = true;
}
stop() {
this._isStarted = false;
}
isStarted() {
return this._isStarted;
}
fps() {
return 60;
}
msBetweenTicks() {
return 1e3 / 60;
}
ticksMissed() {
return 1;
}
simulateLogicTick(time = void 0) {
this._logicQueue.forEach((item) => item.callback(time || {
delta: 1 / this.fps(),
deltaMs: 1e3 / this.fps()
}));
}
simulateDrawTick(time = void 0) {
this._drawQueue.forEach((item) => item.callback(time || {
delta: 1 / this.fps(),
deltaMs: 1e3 / this.fps()
}));
}
simulateLowFps() {
this._lowFpsQueue.forEach((item) => item.callback(this.fps()));
}
};
//#endregion
export { Ticker, TickerMock };

@@ -1,1 +0,223 @@

!function(s,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((s="undefined"!=typeof globalThis?globalThis:s||self).Ticker={})}(this,function(s){Object.defineProperty(s,Symbol.toStringTag,{value:"Module"});var e=class{_minFps;_currentFps=60;_lastChecks=[];_calculateFpsEachMs=2e3;_msSinceLastCalculation=0;_lowFpsInRow=0;_lowFpsCallback;currentFps(){return this._currentFps}calculate(s){this.collectFps(s),this._msSinceLastCalculation+=s.deltaMs,this._msSinceLastCalculation>=this._calculateFpsEachMs&&(this._msSinceLastCalculation=0,this.calculateAverageFps(),this.checkAndNotifyLowFps())}onLowFps(s){this._lowFpsCallback=s}setMinFps(s){return this._minFps=s,this}collectFps(s){this._lastChecks.push(Math.round(1e3/s.deltaMs))}calculateAverageFps(){const s=this._lastChecks.reduce((s,e)=>s+e,0);this._currentFps=Math.round(s/this._lastChecks.length),this._lastChecks=[]}checkAndNotifyLowFps(){this._minFps&&this._currentFps<this._minFps&&(this._lowFpsInRow++,5===this._lowFpsInRow&&(this._lowFpsInRow=0,this._lowFpsCallback&&this._lowFpsCallback()))}},t=class{_fps=new e;_drawQueue=[];_logicQueue=[];_lowFpsQueue=[];_maxFps;_expectedFps;_time={delta:0,deltaMs:0};_lastTimestamp=0;_logicTicksAccumulatedMs=0;_requestFrameId;constructor(s){this.setFps({min:s?.minFps||0,max:s?.maxFps||60,expected:s?.expectedFps||60}),s?.onDrawTick&&this.addDrawTask(s.onDrawTick),s?.onLogicTick&&this.addLogicTask(s.onLogicTick),s?.onLowFps&&this.addLowFpsTask(s.onLowFps),this._fps.onLowFps(()=>{this._lowFpsQueue.forEach(s=>s.callback(this.fps()))})}start(){this._lastTimestamp=performance.now(),this.loop(this._lastTimestamp)}stop(){this._requestFrameId&&(window.cancelAnimationFrame(this._requestFrameId),this._requestFrameId=void 0)}isStarted(){return!!this._requestFrameId}setFps(s){return s.min&&s.min>0&&this._fps.setMinFps(s.min),s.max&&s.max>0&&(this._maxFps=this.compensatedMaxFps(s.max)),s.expected&&s.expected>0&&(this._expectedFps=s.expected),this}addDrawTask(s){return{index:this._drawQueue.push({callback:s})-1,type:"draw"}}addLogicTask(s){return{index:this._logicQueue.push({callback:s})-1,type:"logic"}}addLowFpsTask(s){return{index:this._lowFpsQueue.push({callback:s})-1,type:"low-fps"}}remove(s){switch(s.type){case"draw":this._drawQueue.splice(s.index,1);break;case"logic":this._logicQueue.splice(s.index,1);break;case"low-fps":this._lowFpsQueue.splice(s.index,1)}}fps(){return this._fps.currentFps()}msBetweenTicks(){return this._time.deltaMs}ticksMissed(){return this._time.deltaMs/(1e3/this._expectedFps)}loop(s){const e=1e3/this._maxFps,t=s-this._lastTimestamp;t>=e&&(this._time={delta:t/1e3,deltaMs:t},this._lastTimestamp=s,this._fps.calculate(this._time),this.runQueue()),this._requestFrameId=window.requestAnimationFrame(this.loop.bind(this))}runQueue(){this._logicQueue.length>0&&this.runLogicQueue(),this._drawQueue.length>0&&this.runDrawQueue()}runLogicQueue(){const s=1e3/this._expectedFps;this._logicTicksAccumulatedMs+=this._time.deltaMs;const e={delta:s/1e3,deltaMs:s};for(;this._logicTicksAccumulatedMs>=s;)this._logicQueue.forEach(s=>s.callback(e)),this._logicTicksAccumulatedMs-=s}runDrawQueue(){this._drawQueue.forEach(s=>s.callback(this._time))}compensatedMaxFps(s){return Math.floor(1.1*s)+10*Math.floor(s/100)}};s.Ticker=t,s.TickerMock=class extends t{_isStarted=!1;start(){this._isStarted=!0}stop(){this._isStarted=!1}isStarted(){return this._isStarted}fps(){return 60}msBetweenTicks(){return 1e3/60}ticksMissed(){return 1}simulateLogicTick(s=void 0){this._logicQueue.forEach(e=>e.callback(s||{delta:1/this.fps(),deltaMs:1e3/this.fps()}))}simulateDrawTick(s=void 0){this._drawQueue.forEach(e=>e.callback(s||{delta:1/this.fps(),deltaMs:1e3/this.fps()}))}simulateLowFps(){this._lowFpsQueue.forEach(s=>s.callback(this.fps()))}}});
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.Ticker = {}));
})(this, function(exports) {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region src/fps/fps.ts
var Fps = class {
_minFps;
_currentFps = 60;
_lastChecks = [];
_calculateFpsEachMs = 2e3;
_msSinceLastCalculation = 0;
_lowFpsInRow = 0;
_lowFpsCallback;
currentFps() {
return this._currentFps;
}
calculate(time) {
this.collectFps(time);
this._msSinceLastCalculation += time.deltaMs;
if (this._msSinceLastCalculation >= this._calculateFpsEachMs) {
this._msSinceLastCalculation = 0;
this.calculateAverageFps();
this.checkAndNotifyLowFps();
}
}
onLowFps(callback) {
this._lowFpsCallback = callback;
}
setMinFps(minFps) {
this._minFps = minFps;
return this;
}
collectFps(time) {
this._lastChecks.push(Math.round(1e3 / time.deltaMs));
}
calculateAverageFps() {
const sumFps = this._lastChecks.reduce((a, b) => a + b, 0);
this._currentFps = Math.round(sumFps / this._lastChecks.length);
this._lastChecks = [];
}
checkAndNotifyLowFps() {
if (this._minFps && this._currentFps < this._minFps) {
this._lowFpsInRow++;
if (this._lowFpsInRow === 5) {
this._lowFpsInRow = 0;
if (this._lowFpsCallback) this._lowFpsCallback();
}
}
}
};
//#endregion
//#region src/ticker/ticker.ts
var Ticker = class {
_fps = new Fps();
_drawQueue = [];
_logicQueue = [];
_lowFpsQueue = [];
_maxFps;
_expectedFps;
_time = {
delta: 0,
deltaMs: 0
};
_lastTimestamp = 0;
_logicTicksAccumulatedMs = 0;
_requestFrameId;
constructor(options) {
this.setFps({
min: options?.minFps || 0,
max: options?.maxFps || 60,
expected: options?.expectedFps || 60
});
if (options?.onDrawTick) this.addDrawTask(options.onDrawTick);
if (options?.onLogicTick) this.addLogicTask(options.onLogicTick);
if (options?.onLowFps) this.addLowFpsTask(options.onLowFps);
this._fps.onLowFps(() => {
this._lowFpsQueue.forEach((item) => item.callback(this.fps()));
});
}
start() {
this._lastTimestamp = performance.now();
this.loop(this._lastTimestamp);
}
stop() {
if (this._requestFrameId) {
window.cancelAnimationFrame(this._requestFrameId);
this._requestFrameId = void 0;
}
}
isStarted() {
return !!this._requestFrameId;
}
setFps(options) {
if (options.min && options.min > 0) this._fps.setMinFps(options.min);
if (options.max && options.max > 0) this._maxFps = this.compensatedMaxFps(options.max);
if (options.expected && options.expected > 0) this._expectedFps = options.expected;
return this;
}
addDrawTask(callback) {
return {
index: this._drawQueue.push({ callback }) - 1,
type: "draw"
};
}
addLogicTask(callback) {
return {
index: this._logicQueue.push({ callback }) - 1,
type: "logic"
};
}
addLowFpsTask(callback) {
return {
index: this._lowFpsQueue.push({ callback }) - 1,
type: "low-fps"
};
}
remove(taskId) {
switch (taskId.type) {
case "draw":
this._drawQueue.splice(taskId.index, 1);
break;
case "logic":
this._logicQueue.splice(taskId.index, 1);
break;
case "low-fps":
this._lowFpsQueue.splice(taskId.index, 1);
break;
}
}
fps() {
return this._fps.currentFps();
}
/**
* @deprecated Use `time.deltaMs` provided in draw/logic task callbacks instead.
*/
msBetweenTicks() {
return this._time.deltaMs;
}
/**
* @deprecated Use `time.deltaMs` provided in draw/logic task callbacks instead.
*/
ticksMissed() {
return this._time.deltaMs / (1e3 / this._expectedFps);
}
loop(currentTimestamp) {
const maxDeltaMs = 1e3 / this._maxFps;
const deltaMs = currentTimestamp - this._lastTimestamp;
if (deltaMs >= maxDeltaMs) {
this._time = {
delta: deltaMs / 1e3,
deltaMs
};
this._lastTimestamp = currentTimestamp;
this._fps.calculate(this._time);
this.runQueue();
}
this._requestFrameId = window.requestAnimationFrame(this.loop.bind(this));
}
runQueue() {
if (this._logicQueue.length > 0) this.runLogicQueue();
if (this._drawQueue.length > 0) this.runDrawQueue();
}
runLogicQueue() {
const expectedDeltaMs = 1e3 / this._expectedFps;
this._logicTicksAccumulatedMs += this._time.deltaMs;
const time = {
delta: expectedDeltaMs / 1e3,
deltaMs: expectedDeltaMs
};
while (this._logicTicksAccumulatedMs >= expectedDeltaMs) {
this._logicQueue.forEach((item) => item.callback(time));
this._logicTicksAccumulatedMs -= expectedDeltaMs;
}
}
runDrawQueue() {
this._drawQueue.forEach((item) => item.callback(this._time));
}
compensatedMaxFps(maxFps) {
return Math.floor(maxFps * 1.1) + Math.floor(maxFps / 100) * 10;
}
};
//#endregion
//#region src/ticker/ticker-mock.ts
var TickerMock = class extends Ticker {
_isStarted = false;
start() {
this._isStarted = true;
}
stop() {
this._isStarted = false;
}
isStarted() {
return this._isStarted;
}
fps() {
return 60;
}
msBetweenTicks() {
return 1e3 / 60;
}
ticksMissed() {
return 1;
}
simulateLogicTick(time = void 0) {
this._logicQueue.forEach((item) => item.callback(time || {
delta: 1 / this.fps(),
deltaMs: 1e3 / this.fps()
}));
}
simulateDrawTick(time = void 0) {
this._drawQueue.forEach((item) => item.callback(time || {
delta: 1 / this.fps(),
deltaMs: 1e3 / this.fps()
}));
}
simulateLowFps() {
this._lowFpsQueue.forEach((item) => item.callback(this.fps()));
}
};
//#endregion
exports.Ticker = Ticker;
exports.TickerMock = TickerMock;
});
+30
-11
{
"name": "@armniko/ticker",
"version": "2.1.1",
"description": "Javascript/typescript library for running app loop with separate logical/drawing ticks and FPS limitation.",
"version": "2.2.0",
"description": "A lightweight, zero-dependency JavaScript/TypeScript library for running an application loop with separate logic and draw ticks, FPS limiting, and low-FPS detection.",
"author": "ArmΔ«ns Nikolajevs <armins.nikolajevs@gmail.com>",

@@ -12,10 +12,30 @@ "license": "MIT",

"ticker",
"loop",
"game-loop",
"animation-loop",
"requestanimationframe",
"fps",
"game-loop"
"fps-limit",
"fps-limiter",
"delta-time",
"tick",
"render-loop",
"update-loop",
"frame-rate",
"typescript",
"game-engine"
],
"type": "module",
"sideEffects": false,
"main": "./dist/index.umd.js",
"module": "./dist/index.esm.js",
"typings": "./dist/index.d.ts",
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.esm.js",
"require": "./dist/index.umd.js",
"default": "./dist/index.umd.js"
},
"./package.json": "./package.json"
},
"files": [

@@ -34,3 +54,3 @@ "dist"

"@eslint/js": "^10.0.1",
"@vitest/coverage-v8": "^4.1.4",
"@vitest/coverage-v8": "^4.1.5",
"eslint": "^10.2.1",

@@ -41,9 +61,8 @@ "eslint-config-prettier": "^10.1.8",

"prettier": "^3.8.3",
"terser": "^5.46.1",
"typescript": "^6.0.3",
"typescript-eslint": "^8.58.2",
"vite": "^8.0.8",
"typescript-eslint": "^8.59.0",
"vite": "^8.0.10",
"vite-plugin-dts": "^4.5.4",
"vitest": "^4.1.4"
"vitest": "^4.1.5"
}
}

@@ -8,8 +8,17 @@ # Ticker

Javascript/typescript library for running app loop with separate logical/drawing ticks and FPS limitation.
A lightweight, zero-dependency JavaScript/TypeScript library for running an application loop with **separate logic and
draw ticks**, **FPS limiting**, and **low-FPS detection**.
<hr>
### Why Ticker?
- 🎯 **Decoupled logic & rendering** β€” update game state and draw frames independently
- 🎚️ **FPS control** β€” cap your draw rate to save CPU/battery
- πŸ“‰ **Low-FPS callbacks** β€” react when performance degrades (e.g. lower visual quality)
- πŸ§ͺ **Test-friendly** β€” ships with `TickerMock` for deterministic unit tests
- πŸ“¦ **Zero dependencies** and fully typed
### Installation
```
```bash
npm install @armniko/ticker

@@ -21,5 +30,5 @@ ```

```typescript
import {Ticker, Time} from '@armniko/ticker';
import { Ticker, Time } from '@armniko/ticker';
const element: { position: { x: number, y: number } } = {position: {x: 0, y: 0}};
const element: { position: { x: number, y: number } } = { position: { x: 0, y: 0 } };
const animation: { durationMs: number, distancePx: number } = {

@@ -105,5 +114,13 @@ durationMs: 2000,

<tr>
<td>v2.2.0</td>
<td>
Removed minification of build.<br>
Added <code>exports</code> field for proper module resolution and types.<br>
Marked package as side-effect free.
</td>
</tr>
<tr>
<td>v2.1.1</td>
<td>
Update packages.
Updated packages.
</td>

@@ -129,3 +146,3 @@ </tr>

<td>
Precompile UMD and ESM.
Precompiled UMD and ESM.
</td>

@@ -132,0 +149,0 @@ </tr>