/ shared / components / src / utils / rafQueue.ts
rafQueue.ts
 1  /**
 2   * @name RequestAnimationFrameLimiter
 3   * @description
 4   * allows for multiple callbacks to be called
 5   * within a single RAF function.
 6   * It also spreads long running tasks across multiple
 7   * microtask to help keep the main thread free for user interactions
 8   *
 9   */
10  export class RequestAnimationFrameLimiter {
11      private queue: Array<(timestamp?: number) => void>;
12      private RAF_FN_LIMIT_MS: number;
13      private requestId: number | null;
14      constructor() {
15          this.queue = [];
16          // ideal limit for scroll based animations: https://developers.google.com/web/fundamentals/performance/rendering/optimize-javascript-execution#reduce_complexity_or_use_web_workers
17          this.RAF_FN_LIMIT_MS = 3;
18          this.requestId = null;
19      }
20  
21      private flush(): void {
22          this.requestId =
23              this.queue.length === 0
24                  ? null
25                  : window.requestAnimationFrame((timestamp) => {
26                        const start = window.performance.now();
27                        let ellapsedTime = 0;
28                        const { RAF_FN_LIMIT_MS } = this;
29                        let count = 0;
30  
31                        while (
32                            count < this.queue.length &&
33                            ellapsedTime < RAF_FN_LIMIT_MS
34                        ) {
35                            let item = this.queue[count];
36                            if (item) {
37                                item(timestamp);
38                            }
39                            const finishTime = window.performance.now();
40  
41                            count = count + 1;
42                            ellapsedTime = finishTime - start;
43                        }
44                        const newQueue = this.queue.slice(count);
45  
46                        this.queue = newQueue;
47                        this.flush();
48                    });
49      }
50      public add(callback: () => void): void {
51          this.queue.push(callback);
52          if (this.requestId === null) {
53              this.flush();
54          }
55      }
56  }
57  
58  let raf: RequestAnimationFrameLimiter | ServerSafeRAFLimiter = null;
59  
60  type ServerSafeRAFLimiter = {
61      add: (callback: () => void) => void;
62  };
63  
64  export const getRafQueue = () => {
65      if (typeof window === 'undefined') {
66          // SSR safe
67          raf = {
68              add: (callback: () => void) => callback(),
69          };
70      } else if (raf === null) {
71          raf = new RequestAnimationFrameLimiter();
72      }
73      return raf;
74  };