Skip to content

Instantly share code, notes, and snippets.

@samwho
Created April 1, 2025 08:33
Show Gist options
  • Select an option

  • Save samwho/66513859aa222b497d7ea64ecc774875 to your computer and use it in GitHub Desktop.

Select an option

Save samwho/66513859aa222b497d7ea64ecc774875 to your computer and use it in GitHub Desktop.
import GUI from "lil-gui";
import { div } from "./DOM";
let id = 0;
function getUniqueID(): string {
return `element-${id++}`;
}
export class BaseElement extends HTMLElement {
gui?: GUI;
debug!: boolean;
onDisconnectCallbacks: (() => void)[] = [];
_initDone = false;
afterInitCallbacks: (() => void)[] = [];
isParsed() {
let el: Node | null = this;
while (true) {
if (el.nextSibling) return true;
el = el.parentNode;
if (!el) return false;
}
}
connectedCallback() {
if (this.ownerDocument.readyState === "complete" || this.isParsed()) {
this.parsedCallback();
return;
}
const callback = () => {
observer.disconnect();
this.ownerDocument.removeEventListener("DOMContentLoaded", callback);
this.parsedCallback();
};
const observer = new MutationObserver(() => {
if (this.isParsed()) {
callback();
}
});
this.ownerDocument.addEventListener("DOMContentLoaded", () => {
callback();
});
if (!this.parentNode) {
throw new Error("Element must have a parent node");
}
observer.observe(this.parentNode, { childList: true, subtree: true });
}
parsedCallback() {
if (!this.id) {
this.id = getUniqueID();
}
this.debug = this.getAttribute("debug") === "true";
if (this.debug) {
this.gui = new GUI({
container: this,
width: this.clientWidth,
});
}
let resizing = false;
const elementObserver = new ResizeObserver((entries) => {
if (resizing) return;
resizing = true;
requestAnimationFrame(() => {
try {
this.onResize();
} finally {
resizing = false;
}
});
});
elementObserver.observe(this);
this.onDisconnect(() => elementObserver.disconnect());
let pageResizing = false;
const windowResizeHandler = async () => {
if (pageResizing) return;
pageResizing = true;
requestAnimationFrame(() => {
try {
this.onPageResize();
} finally {
pageResizing = false;
}
});
};
window.addEventListener("resize", windowResizeHandler);
this.onDisconnect(() => {
window.removeEventListener("resize", windowResizeHandler);
});
const intersectionObserver = new IntersectionObserver(
async (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
this.onVisible();
} else {
this.onHidden();
}
}
},
{
threshold: 0,
rootMargin: "5px",
}
);
intersectionObserver.observe(this);
this.onDisconnect(() => {
intersectionObserver.disconnect();
});
this.init();
this._initDone = true;
for (const callback of this.afterInitCallbacks) {
callback();
}
}
onDisconnect(callback: () => void) {
this.onDisconnectCallbacks.push(callback);
}
disconnectedCallback() {
for (const callback of this.onDisconnectCallbacks) {
callback();
}
}
afterInit(callback: () => void) {
if (this._initDone) {
callback();
return;
}
this.afterInitCallbacks.push(callback);
}
init() {}
onResize() {}
onPageResize() {}
onVisible() {}
onHidden() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment