Files
apps.apple.com/shared/components/src/utils/rafQueue.ts
Akshat Mehta edd5728428 main code
2025-11-23 11:03:57 +05:30

75 lines
2.4 KiB
TypeScript

/**
* @name RequestAnimationFrameLimiter
* @description
* allows for multiple callbacks to be called
* within a single RAF function.
* It also spreads long running tasks across multiple
* microtask to help keep the main thread free for user interactions
*
*/
export class RequestAnimationFrameLimiter {
private queue: Array<(timestamp?: number) => void>;
private RAF_FN_LIMIT_MS: number;
private requestId: number | null;
constructor() {
this.queue = [];
// ideal limit for scroll based animations: https://developers.google.com/web/fundamentals/performance/rendering/optimize-javascript-execution#reduce_complexity_or_use_web_workers
this.RAF_FN_LIMIT_MS = 3;
this.requestId = null;
}
private flush(): void {
this.requestId =
this.queue.length === 0
? null
: window.requestAnimationFrame((timestamp) => {
const start = window.performance.now();
let ellapsedTime = 0;
const { RAF_FN_LIMIT_MS } = this;
let count = 0;
while (
count < this.queue.length &&
ellapsedTime < RAF_FN_LIMIT_MS
) {
let item = this.queue[count];
if (item) {
item(timestamp);
}
const finishTime = window.performance.now();
count = count + 1;
ellapsedTime = finishTime - start;
}
const newQueue = this.queue.slice(count);
this.queue = newQueue;
this.flush();
});
}
public add(callback: () => void): void {
this.queue.push(callback);
if (this.requestId === null) {
this.flush();
}
}
}
let raf: RequestAnimationFrameLimiter | ServerSafeRAFLimiter = null;
type ServerSafeRAFLimiter = {
add: (callback: () => void) => void;
};
export const getRafQueue = () => {
if (typeof window === 'undefined') {
// SSR safe
raf = {
add: (callback: () => void) => callback(),
};
} else if (raf === null) {
raf = new RequestAnimationFrameLimiter();
}
return raf;
};