summaryrefslogtreecommitdiff
path: root/shared/components/src/utils/rafQueue.ts
diff options
context:
space:
mode:
Diffstat (limited to 'shared/components/src/utils/rafQueue.ts')
-rw-r--r--shared/components/src/utils/rafQueue.ts74
1 files changed, 74 insertions, 0 deletions
diff --git a/shared/components/src/utils/rafQueue.ts b/shared/components/src/utils/rafQueue.ts
new file mode 100644
index 0000000..a56d9a7
--- /dev/null
+++ b/shared/components/src/utils/rafQueue.ts
@@ -0,0 +1,74 @@
+/**
+ * @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;
+};