@checksub_team/peaks_timeline
Version:
JavaScript UI component for displaying audio waveforms
298 lines (255 loc) • 7.51 kB
JavaScript
/**
* @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;
});