How to check if an element has been loaded on a page before running a script?
I would recommend against using MutationObserver. This approach has many drawbacks: it slows page loading, has poor browser support. There are situations when this is the only approach to solve the problem at hand.
A better approach is to use the onload DOM event. This event works on tags such as iframe and img. For other tags, this is the pseudo code you can take inspiration from:
<body>
<script>
function divLoaded(event){
alert(event.target.previousSibling.previousSibling.id);
}
</script>
<div id="element_to_watch">This is an example div to watch it's loading</div>
<iframe style="display: none; width: 0; height: 0" onload="divLoaded(event)"/>
</body>
NOTE: The trick is to attach an iframe or img element after every element you want to watch for their loading.
Sounds like a job for MutationObserver
!
A MutationObserver
is like an event listener: you can attach it to any DOM element to listen for changes:
var observer = new MutationObserver(function (mutationRecords) {
console.log("change detected");
});
The callback is passed an array of MutationRecord
s, which hold different lists of added/deleted/modified nodes.
Then, we attach the observer to any node:
observer.observe(document.body, {childList: true});
// second argument is a config: in this case, only fire the callback when nodes are added or removed
Note: IE support is not amazing. (surprise, surprise) Only works on IE 11. (Edge supports it, though.)
Here's another SO question about detecting changes to the DOM: Detect changes in the DOM
Snippet:
document.querySelector("button").addEventListener("click", function () {
document.body.appendChild(document.createElement("span"));
});
var observer = new MutationObserver(function (m) {
if (m[0].addedNodes[0].nodeName === "SPAN")
document.querySelector("div").innerHTML += "Change Detected<br>";
});
observer.observe(document.body, {childList: true});
<button>Click</button>
<div></div>
2022 Version, with MutationObserver
Rewrote the 2020 code to use a MutationObserver
instead of polling.
/**
* Wait for an element before resolving a promise
* @param {String} querySelector - Selector of element to wait for
* @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout
*/
function waitForElement(querySelector, timeout){
return new Promise((resolve, reject)=>{
var timer = false;
if(document.querySelectorAll(querySelector).length) return resolve();
const observer = new MutationObserver(()=>{
if(document.querySelectorAll(querySelector).length){
observer.disconnect();
if(timer !== false) clearTimeout(timer);
return resolve();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
if(timeout) timer = setTimeout(()=>{
observer.disconnect();
reject();
}, timeout);
});
}
waitForElement("#idOfElementToWaitFor", 3000).then(function(){
alert("element is loaded.. do stuff");
}).catch(()=>{
alert("element did not load in 3 seconds");
});
2020 version, with promises
This code will poll the DOM 10x/second and resolve a promise if it's found before the optional timeout.
/**
* Wait for an element before resolving a promise
* @param {String} querySelector - Selector of element to wait for
* @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout
*/
function waitForElement(querySelector, timeout=0){
const startTime = new Date().getTime();
return new Promise((resolve, reject)=>{
const timer = setInterval(()=>{
const now = new Date().getTime();
if(document.querySelector(querySelector)){
clearInterval(timer);
resolve();
}else if(timeout && now - startTime >= timeout){
clearInterval(timer);
reject();
}
}, 100);
});
}
waitForElement("#idOfElementToWaitFor", 3000).then(function(){
alert("element is loaded.. do stuff");
}).catch(()=>{
alert("element did not load in 3 seconds");
});
Original answer
function waitForElement(id, callback){
var poops = setInterval(function(){
if(document.getElementById(id)){
clearInterval(poops);
callback();
}
}, 100);
}
waitForElement("idOfElementToWaitFor", function(){
alert("element is loaded.. do stuff");
});