Skip to content

Instantly share code, notes, and snippets.

@mindplay-dk
Last active March 25, 2024 10:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mindplay-dk/d2c25eb96fb707a4749e08666e8aea31 to your computer and use it in GitHub Desktop.
Save mindplay-dk/d2c25eb96fb707a4749e08666e8aea31 to your computer and use it in GitHub Desktop.
waitForElement function (wait for element matching selector)
let animationCounter = 0;
export function waitForElement(selector) {
return new Promise((resolve) => {
const elem = document.querySelector(selector);
if (elem) {
resolve(elem); // already in the DOM
}
const animationName = `waitForElement__${animationCounter++}`;
const style = document.createElement("style");
const keyFrames = `
@keyframes ${animationName} {
from { opacity: 1; }
to { opacity: 1; }
}
${selector} {
animation-duration: 1ms;
animation-name: ${animationName};
}
`;
style.appendChild(new Text(keyFrames));
document.head.appendChild(style);
const eventListener = (event) => {
if (event.animationName === animationName) {
cleanUp();
resolve(document.querySelector(selector));
}
};
function cleanUp() {
document.removeEventListener("animationstart", eventListener);
document.head.removeChild(style);
}
document.addEventListener("animationstart", eventListener, false);
});
}

What is this witchcraft? 😄

I needed a way to wait for an HTML element, matching a given CSS selector, to get added to the DOM.

waitForElement("#popup.annoying").then(el => {
  el.remove();
});

I tried the usual approaches with MutationObserver, polling, etc. - none of it responded quickly enough, many of the existing solutions have performance issues, and a few of them don't actually work for selectors like ul.foo > li.bar if the foo class gets added after the li.bar elements have already been added.

Then it hit me - the CSS engine is already doing this work using extremely sophisticated optimizations, why don't we just let the CSS engine do the work? Inject a "CSS animation", and wait for the "animation" to start. It's a rugged approach, no doubt - but it's simple, and fast.

On the "fast" part, I can't actually prove this - I have no idea how you'd even benchmark something like this. But the more common approaches, like searching the entire DOM in response to mutations or timers, definitely made my script noticeably slower.

Prior art:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment