You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

75 lines
2.1 KiB

const FRAME_BUDGET = 1000 / 60; // ~16.67ms → 60fps cap
/**
* Creates a throttled requestAnimationFrame scheduler that limits
* callback execution to ~60fps regardless of display refresh rate.
*
* On 144Hz displays, RAF fires every ~6.9ms. This utility coalesces
* rapid schedule() calls and skips execution when less than one 60fps
* frame budget has elapsed, so only the latest callback runs at most
* once per ~16ms.
*
* For event-driven use (hover, ResizeObserver): the event source
* keeps firing, so skipped frames naturally get picked up by the
* next event.
*/
export function createThrottledRAF() {
let rafId: number | null = null;
let lastTime: number | null = null;
function schedule(callback: () => void): void {
if (rafId !== null) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame((now) => {
rafId = null;
if (lastTime !== null && now - lastTime < FRAME_BUDGET) return;
lastTime = now;
callback();
});
}
function cancel(): void {
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
}
return { schedule, cancel };
}
/**
* Creates a continuous animation loop throttled to ~60fps.
*
* Unlike createThrottledRAF (which is event-driven and relies on
* external events to re-trigger), this runs a self-scheduling loop
* that executes the callback at most once per ~16ms frame budget.
* The RAF loop itself runs at the display's native rate but skips
* work on intermediate frames.
*/
export function createThrottledLoop(callback: () => void) {
let rafId: number | null = null;
let lastTime = 0;
function tick(now: number) {
if (now - lastTime >= FRAME_BUDGET) {
lastTime = now;
callback();
}
rafId = requestAnimationFrame(tick);
}
function start(): void {
if (rafId !== null) return;
lastTime = 0; // Execute immediately on first frame
rafId = requestAnimationFrame(tick);
}
function stop(): void {
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
}
return { start, stop };
}