monte
Version:
A visualization framework for D3.js and SVG. Ships with prebuilt charts and components.
140 lines (116 loc) • 3.78 kB
JavaScript
// Based on examples at https://developer.mozilla.org/en-US/docs/Web/Events/resize
import { mergeOptions } from '../tools/mergeOptions';
// Frames per second -> Frame duration in MS == the timeout in MS to use
// -----------------------------
// | Frame Rate | Timeout in MS |
// | 30fps | 33ms |
// | 20fps | 50ms |
// | 15fps | 66ms |
// | 10fps | 100ms |
// | 5fps | 200ms |
// ------------------------------
export const frameRateTimings = {
FPS_30: 33,
FPS_20: 50,
FPS_15: 66,
FPS_10: 100,
FPS_05: 200,
};
export function EventWatcherException(message) {
this.message = message;
this.name = 'EventWatcherException';
}
const DEFAULTS = {
timeoutDelayMs: frameRateTimings.FPS_15,
target: window,
eventName: 'resize',
// Automatically run all callbacks when `document.DOMContentLoaded` or `window.load` is fired.
documentLoadRun: true,
};
export class EventWatcher {
constructor(options) {
this.opts = mergeOptions(options, DEFAULTS);
this.callbacks = [];
this.running = false;
this.pendingFrame = null;
this.pendingTimeout = null;
this.listenerAttached = false;
this.documentReady = null;
if (!this.opts.eventName) {
throw new EventWatcher.EventWatcherException('An event name must be given!');
}
if (this.opts.documentLoadRun) {
const eventWatcher = this;
const ready = (() => {
// Use timeout to get a fresh pass after document is ready.
setTimeout(function() {
document.removeEventListener('DOMContentLoaded', ready);
window.removeEventListener('load', ready);
eventWatcher.documentReady = true;
eventWatcher.runCallbacks();
}, 0);
});
this.documentReady = false;
document.addEventListener('DOMContentLoaded', ready);
window.addEventListener('load', ready);
if (document.readyState === 'complete') {
ready();
}
}
}
// fired on event firing
_fired() {
if (!this.running) {
this.running = true;
const run = this._runCallbacks.bind(this);
if (window.requestAnimationFrame) {
this.pendingFrame = window.requestAnimationFrame(run);
}
else {
this.pendingTimeout = setTimeout(run, this.opts.timeoutDelayMs);
}
}
}
// Run the actual callbacks and clear state.
_runCallbacks() {
this.pendingFrame = this.pendingTimeout = null;
this.runCallbacks();
this.running = false;
}
// Run the actual callbacks.
runCallbacks() {
this.callbacks.forEach((callback) => callback());
}
// Add callback
add(callback) {
if (!this.listenerAttached) {
this.opts.target.addEventListener(this.opts.eventName, this._fired.bind(this));
this.listenerAttached = true;
}
if (callback) { this.callbacks.push(callback); }
}
// Remove specific callback
remove(callback) {
if (this.callbacks.length) {
const index = this.callbacks.indexOf(callback);
if (index > -1) { this.callbacks.splice(index, 1); }
}
return this;
}
// Destroy EventWatcher
destroy() {
// Cancel pending invocation
if (this.pendingFrame) { window.cancelAnimationFrame(this.pendingFrame); }
else if (this.pendingTimeout) { clearTimeout(this.pendingTimeout); }
// Remove listener
if (this.listenerAttached) {
this.opts.target.removeEventListener(this.opts.eventName, this._fired.bind(this));
}
}
// Modify the delay. The delay is only used in browsers that don't support animation frames.
timeoutDelay(timeoutDelayMs = null) {
if (timeoutDelayMs === null) { return this.opts.timeoutDelayMs;}
this.opts.timeoutDelayMs = timeoutDelayMs;
return this;
}
}