UNPKG

@checksub_team/peaks_timeline

Version:

JavaScript UI component for displaying audio waveforms

298 lines (255 loc) 7.51 kB
/** * @file * * Defines the {@link invoker} class. * * @module invoker */ define([ ], function() { 'use strict'; // Use requestAnimationFrame for smoother animations when available var raf = (typeof window !== 'undefined' && window.requestAnimationFrame) || function(fn) { return setTimeout(fn, 16); }; var caf = (typeof window !== 'undefined' && window.cancelAnimationFrame) || function(id) { clearTimeout(id); }; /** * An invoker class for throttling and performance optimization. * * @class * @alias Invoker */ function Invoker() { this._throttledFunc = {}; this._rafThrottled = {}; this._frameCallbacks = []; this._frameScheduled = false; } /** * Throttle using setInterval - good for consistent timing */ Invoker.prototype.throttle = function(id, func, wait) { var self = this; if (this._throttledFunc[id]) { // Already limiting this._throttledFunc[id].func = func; this._throttledFunc[id].continue = true; } else { // Create a limit this._throttledFunc[id] = { func: func, timer: null, continue: true }; this._throttledFunc[id].timer = setInterval(function() { if (self._throttledFunc[id].continue) { func(); self._throttledFunc[id].continue = false; } else { clearInterval(self._throttledFunc[id].timer); delete self._throttledFunc[id]; } }, wait); } }; /** * Throttle using requestAnimationFrame - ideal for visual updates * Ensures function runs at most once per animation frame */ Invoker.prototype.throttleRAF = function(id, func) { var self = this; if (this._rafThrottled[id]) { // Update the function but don't schedule again this._rafThrottled[id].func = func; this._rafThrottled[id].pending = true; return; } this._rafThrottled[id] = { func: func, pending: true, rafId: null }; function frame() { if (self._rafThrottled[id] && self._rafThrottled[id].pending) { self._rafThrottled[id].pending = false; self._rafThrottled[id].func(); self._rafThrottled[id].rafId = raf(frame); } else if (self._rafThrottled[id]) { delete self._rafThrottled[id]; } } this._rafThrottled[id].rafId = raf(frame); }; /** * Cancel a RAF-throttled function */ Invoker.prototype.cancelThrottleRAF = function(id) { if (this._rafThrottled[id]) { if (this._rafThrottled[id].rafId) { caf(this._rafThrottled[id].rafId); } delete this._rafThrottled[id]; } }; /** * Schedule a callback for the next animation frame * Multiple callbacks are batched together */ Invoker.prototype.scheduleFrame = function(callback) { var self = this; this._frameCallbacks.push(callback); if (!this._frameScheduled) { this._frameScheduled = true; raf(function() { self._frameScheduled = false; var callbacks = self._frameCallbacks; self._frameCallbacks = []; for (var i = 0; i < callbacks.length; i++) { callbacks[i](); } }); } }; /** * Creates a debounced function */ Invoker.prototype.debounce = function(func, wait, immediate) { var timeout; return function executedFunction() { // eslint-disable-next-line consistent-this var _self = this; var args = arguments; function later() { timeout = null; if (!immediate) { func.apply(_self, args); } } var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { func.apply(_self, args); } }; }; /** * Creates a leading-edge throttled function using RAF * Executes immediately, then throttles subsequent calls */ Invoker.prototype.throttleLeading = function(func) { var scheduled = false; var savedArgs = null; var savedThis = null; return function throttled() { if (scheduled) { savedArgs = arguments; // eslint-disable-next-line consistent-this savedThis = this; return; } func.apply(this, arguments); scheduled = true; raf(function() { scheduled = false; if (savedArgs) { func.apply(savedThis, savedArgs); savedArgs = null; savedThis = null; } }); }; }; /** * Creates a trailing-edge throttled function using RAF * Collects calls and executes only the last one per frame */ Invoker.prototype.throttleTrailing = function(func) { var rafId = null; var savedArgs = null; var savedThis = null; return function throttled() { savedArgs = arguments; // eslint-disable-next-line consistent-this savedThis = this; if (rafId === null) { rafId = raf(function() { rafId = null; func.apply(savedThis, savedArgs); }); } }; }; /** * Batch multiple function calls into a single execution * Useful for accumulating updates before rendering */ Invoker.prototype.createBatcher = function(processFn, options) { options = options || {}; var maxWait = options.maxWait || 100; var items = []; var timeout = null; var lastFlush = 0; function flush() { if (items.length === 0) { return; } var batch = items; items = []; timeout = null; lastFlush = Date.now(); processFn(batch); } return { add: function(item) { items.push(item); if (timeout === null) { var timeSinceLastFlush = Date.now() - lastFlush; if (timeSinceLastFlush >= maxWait) { raf(flush); } else { timeout = setTimeout(flush, Math.min(16, maxWait - timeSinceLastFlush)); } } }, flush: flush, size: function() { return items.length; } }; }; /** * Destroy all pending operations */ Invoker.prototype.destroy = function() { var id; // Clear interval-based throttles for (id in this._throttledFunc) { if (Object.prototype.hasOwnProperty.call(this._throttledFunc, id)) { clearInterval(this._throttledFunc[id].timer); } } this._throttledFunc = {}; // Clear RAF-based throttles for (id in this._rafThrottled) { if (Object.prototype.hasOwnProperty.call(this._rafThrottled, id)) { if (this._rafThrottled[id].rafId) { caf(this._rafThrottled[id].rafId); } } } this._rafThrottled = {}; // Clear frame callbacks this._frameCallbacks = []; this._frameScheduled = false; }; return Invoker; });