Skip to content

Instantly share code, notes, and snippets.

@ookkoouu
Last active February 14, 2024 11:47
Show Gist options
  • Save ookkoouu/7220fad9a86c9e3a0ce015feb9aca22f to your computer and use it in GitHub Desktop.
Save ookkoouu/7220fad9a86c9e3a0ce015feb9aca22f to your computer and use it in GitHub Desktop.
DOM Observer like as jQuery
function observeMutation(
target: Element,
callback: (muts: MutationRecord[]) => void,
options?: MutationObserverInit
) {
const obs = new window.MutationObserver(callback);
obs.observe(target, options);
return () => obs.disconnect();
}
function isElement(node: Node): node is Element {
return node.nodeType === Node.ELEMENT_NODE || node instanceof Element;
}
type WatcherCallback = (element: Element) => void;
type Watcher = {
selector: string;
callback: WatcherCallback;
};
type Unwatcher = () => void;
class DOMObserver {
protected disconnect: () => void = () => {};
protected addedWatchers = new Set<Watcher>();
protected removedWatchers = new Set<Watcher>();
protected attributeWatchers = new Set<Watcher>();
constructor() {
this.start();
}
start(): void {
this.disconnect = observeMutation(
document.documentElement,
(muts) => this.processMutation(muts),
{
childList: true,
subtree: true,
attributes: true,
}
);
}
stop(): void {
this.disconnect();
}
added(selector: string, callback: WatcherCallback): Unwatcher {
const w = { selector, callback };
this.addedWatchers.add(w);
return () => this.addedWatchers.delete(w);
}
removed(selector: string, callback: WatcherCallback): Unwatcher {
const w = { selector, callback };
this.removedWatchers.add(w);
return () => this.removedWatchers.delete(w);
}
attribute(selector: string, callback: WatcherCallback): Unwatcher {
const w = { selector, callback };
this.attributeWatchers.add(w);
return () => this.attributeWatchers.delete(w);
}
protected processMutation(muts: MutationRecord[]) {
if (
this.addedWatchers.size == 0 &&
this.removedWatchers.size == 0 &&
this.attributeWatchers.size == 0
) {
return;
}
muts.forEach((mut) => {
switch (mut.type) {
case "childList":
if (this.addedWatchers.size > 0 || this.removedWatchers.size > 0) {
this.processChild(mut);
}
break;
case "attributes":
if (this.attributeWatchers.size > 0) {
this.processAttribute(mut);
}
break;
}
});
}
protected processChild(mut: MutationRecord) {
const processCallback = (elm: Element, w: Watcher) => {
if (elm.matches(w.selector)) {
w.callback(elm);
}
elm.querySelectorAll<Element>(w.selector).forEach((e) => w.callback(e));
};
mut.addedNodes.forEach((node) => {
if (!isElement(node)) {
return;
}
this.addedWatchers.forEach((w) => processCallback(node, w));
});
mut.removedNodes.forEach((node) => {
if (!isElement(node)) {
return;
}
this.removedWatchers.forEach((w) => processCallback(node, w));
});
}
protected processAttribute(mut: MutationRecord) {
const elm = mut.target;
if (!isElement(elm)) {
return;
}
this.attributeWatchers.forEach((w) => {
if (elm.matches(w.selector)) {
w.callback(elm);
}
});
}
}
export default new DOMObserver();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment