Last active
June 14, 2024 10:15
-
-
Save aayla-secura/fb03e3ea3e6553c5a9208d4273617c89 to your computer and use it in GitHub Desktop.
Javascript Resize Observer wrapper with additions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// supports: | |
// - throttling by calling handler only once after a given delay has passed since the last resize | |
// - pausing and resuming (with or without calling callback at resume) | |
// - observing multiple targets and ensuring the handler is called only once all are added | |
// - observing targets without calling the handler initially when adding, only when later resized | |
class ResizeObserverExt { | |
observer = null; | |
#waitingResolver = null; | |
#targets = new Set(); | |
#isPaused = false; | |
#skip = new Set(); | |
constructor(callback, delay = 0) { | |
this.#waitingResolver = new WaitingResolver( | |
this.#getCallbackWrapper(callback, delay), | |
); | |
this.observer = new ResizeObserver((entries) => { | |
this.#waitingResolver.add(...entries); | |
}); | |
} | |
observe(...targets) { | |
// call handler only once all are added | |
this.#waitingResolver.waitForMore(targets.length); | |
targets.forEach((target) => { | |
if (this.#targets.has(target)) { | |
console.log("target already present", target); | |
} else { | |
console.log("adding target", target); | |
this.#targets.add(target); | |
this.observer.observe(target); | |
} | |
}); | |
} | |
// does not call the handler when adding the targets | |
observeLater(...targets) { | |
targets.forEach((target) => { | |
this.#skip.add(target); | |
}); | |
this.observe(...targets); | |
} | |
unobserve(target) { | |
if (this.#targets.has(target)) { | |
console.log("removing target", target); | |
this.#targets.delete(target); | |
this.observer.unobserve(target); | |
} else { | |
console.log("no such target", target); | |
} | |
} | |
disconnect() { | |
console.log("removing all targets"); | |
this.#targets.clear(); | |
return this.observer.disconnect(); | |
} | |
pause() { | |
if (this.#isPaused) { | |
console.log("already paused"); | |
return; | |
} | |
console.log("pausing"); | |
this.#isPaused = true; | |
this.observer.disconnect(); | |
} | |
resume(skipCallback = false) { | |
if (!this.#isPaused) { | |
console.log("not paused"); | |
return; | |
} | |
console.log("resuming"); | |
if (skipCallback) { | |
this.#skip = this.#skip.union(this.#targets); | |
} | |
this.#isPaused = false; | |
this.#targets.forEach((el) => { | |
this.observer.observe(el); | |
}); | |
} | |
static #getFuncDelayed(f, delay) { | |
var timer = 0; | |
return function (...args) { | |
clearTimeout(timer); | |
timer = setTimeout(() => f.apply(this, args), delay); | |
}; | |
} | |
#getCallbackWrapper(callback, delay) { | |
const wrapped = (entries, observer) => { | |
var selectedEntries = entries.filter((entry) => { | |
if (this.#skip.has(entry.target)) { | |
this.#skip.delete(entry.target); | |
return false; | |
} | |
return true; | |
}); | |
if (selectedEntries.length > 0) { | |
callback(selectedEntries, observer); | |
} | |
}; | |
if (delay > 0) { | |
return this.constructor.#getFuncDelayed(wrapped, delay); | |
} | |
return wrapped; | |
} | |
} | |
class WaitingResolver { | |
#callback = null; | |
#waitFor = 0; | |
#buffer = []; | |
constructor(callback, waitFor = 0) { | |
if ((!callback) instanceof Function) { | |
throw "Callback must be a function"; | |
} | |
this.#callback = callback; | |
this.#waitFor = waitFor; | |
} | |
waitForMore(n) { | |
this.#waitFor += n; | |
} | |
add(...values) { | |
this.#buffer.push(...values); | |
if (this.#buffer.length >= this.#waitFor) { | |
this.#callback(this.#buffer); | |
this.#buffer = []; | |
this.#waitFor = 0; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment