478

I'm working on an Extension in Chrome, and I'm wondering: what's the best way to find out when an element comes into existence? Using plain javascript, with an interval that checks until an element exists, or does jQuery have some easy way to do this?

Nakilon
  • 34,866
  • 14
  • 107
  • 142
mattsven
  • 22,305
  • 11
  • 68
  • 104
  • 1
    Looks like every single option here today (including from comments) is either outdated or incomplete. They don't consider @hughsk's awesome input fully, the compatibility argument. Meanwhile I'd recommend simply using Brandon's update on Ryan's answer for general simplicity and less risk of overhead, I suppose. – cregox Apr 01 '15 at 23:17
  • 5
    `MutationObserver` > `DOM Mutation Events` > `setTimeout`. – mattsven Apr 02 '15 at 15:28
  • 3
    Not from where I stand. `setTimeout` is compatible, simple to implement, simple to maintain, and has negligible overhead. – cregox Apr 02 '15 at 16:07
  • `setTimeout` + `jQuery` is less than ideal in my opinion for two reasons: 1.) jQuery bloat 2.) you're needlessly manually querying the DOM for elements, events beat that speed-wise easily, 3.) it will always be slower than any native implementation. If you need to do anything based on the presence of an element reasonably quickly, especially if seamless user experience is your goal, it is inferior. – mattsven Apr 02 '15 at 19:21
  • That said, that is why **I** dislike it. The other solutions are more robust, immediate, less likely to break or suffer from bugs, and most importantly more detailed as to what changes occurred. If you are working in a situation where you have complete control or only need DOM change updates for simple, uncomplicated elements hierarchies it's probably great. – mattsven Apr 02 '15 at 19:23
  • 1
    *(...I can't count)* – mattsven Apr 02 '15 at 19:26
  • 9
    There are 3 kinds of people: those who can count and those who can't. ;P – cregox Apr 03 '15 at 04:46
  • 1
    @mattsven I think you could consider mark my answer as accepted, because the currently accepted answer is fully useless, specially for beginners, is just a bad copy paste of the documentation. I spent many hours understing how it works and how to make a simple working example. And if you read the comments of accepted answer they think `MutationObserver` is hard and complex when it isn´t. My answer can help future visitors in their own context. Regards. – SilverSurfer Aug 14 '20 at 09:58

27 Answers27

531

Here is a simple solution using the MutationObserver api.

  1. No jQuery
  2. No Timer
  3. No third party libraries
  4. Promise based and works well with async/await

I have used it in several projects.

function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                observer.disconnect();
                resolve(document.querySelector(selector));
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

To use it:

waitForElm('.some-class').then((elm) => {
    console.log('Element is ready');
    console.log(elm.textContent);
});

Or with async/await:

const elm = await waitForElm('.some-class');
andrybak
  • 2,129
  • 2
  • 20
  • 40
Yong Wang
  • 5,812
  • 2
  • 12
  • 13
  • 8
    This is neat! The cool part about it is that you could use it with `async` / `await` too. You might also be able squeeze more performance out of it by doing `mutations.addedNodes.find(node => node.matchesSelector("..."))` – mattsven Apr 29 '20 at 21:37
  • @mattsven Good point! Checking just the nodes in the mutations is more performant than doing document.querySelector. – Yong Wang Apr 29 '20 at 22:56
  • This is awesome, thanks. But what is the purpose of that `mutations` param inside the `observer` constant? – Ralph David Abernathy Jul 14 '21 at 23:09
  • 3
    @RalphDavidAbernathy, you are right, the `mutations` param is not used in the code and can be safely deleted. It has a lot of useful information on what is mutated. I put it there just in case you need to access it. – Yong Wang Oct 22 '21 at 16:59
  • 1
    Finally! I tried everything in the book, but couldn't fetch the element by class. I even ran the function on window.load but no help. This did it! Thanks @YongWang – ASR4 Jan 23 '22 at 05:39
  • 2
    This function is checking the item coming in DOM for first time, how to check if item coming more times, so every time it should run, – Atul Rajput Apr 05 '22 at 04:21
  • 2
    This appears to override any other code that monitors the targeted element(s) for events. I have a page where I'm trying to watch for changes to an input based on other code that I do not control (within my own user script, monitoring a form from a website, conditionally applying formatting). The above code causes the page's own JS to cease functioning for the targeted element(s). Is there any way to monitor an element, **without clobbering** any existing event watchers? Perhaps similar to jQuery's `noConflict()`? – Eiríkr Útlendi Apr 26 '22 at 19:56
  • I'm passing as a parameter the value ('#'+num), where num is '123456' as an example. And having an error 'selector is not valid'(((. Using getElementById() or jquery fixes this and everything works... How to deal with this? – Oleksii Morenets Aug 01 '22 at 18:53
  • https://rules.sonarsource.com/javascript/RSPEC-3801 - seems having the return there is a major code smell - removed and added various logs to see if that's an issue (if I'm just re-adding the observer in an annoying way) - but it turns out not. So can safely remove the return statement in the if there... – Julix Nov 19 '22 at 17:13
  • 3
    @Julix, the if-return statement is an optimization for the case that the element if already present when you call this function. The promise will be resolved directly without having to mess with MutationObserver – Yong Wang Dec 05 '22 at 02:41
  • Good point, Yong. - Would you then add an empty return at the end or how would you comply with 'Functions should use "return" consistently'? – Julix Dec 05 '22 at 20:16
  • The return here is just used to stop executing the rest codes. In that case, you can remove the return, and wrap the remaining code in a else block – Yong Wang Dec 06 '22 at 22:57
  • This differs from the oldschool timeout+querySelector approach. For example, if you have a
    on the page, and you wait for the selector ".foo", then later you div.addClass("foo"). The selector now resolves, but the waitFor utility doesn't find it.
    – 000 Feb 22 '23 at 05:01
  • How do repeated calls to `document.querySelector(selector)` perform? What I mean by that: would it be useful to cache the value before the `if`, so that the then-branch can also use it? – andrybak Jun 14 '23 at 18:19
  • @EiríkrÚtlendi Did you find a way to simulate the `noConflict()` functionality? – enissay Jun 15 '23 at 12:22
  • 1
    @enissay, unfortunately, no. We gave up on the idea. It was only a nice-to-have for us anyway, flagging a particular value if out of expected range. While more error-prone, we've just asked our humans to keep their eyes peeled. :) – Eiríkr Útlendi Jun 15 '23 at 17:49
  • Wouldn't it be better to swap around calls to `resolve(...)` and `observer.disconnect()`? What if the lambda passed into `.then()` causes more mutations? – andrybak Aug 19 '23 at 14:52
231

DOMNodeInserted is being deprecated, along with the other DOM mutation events, because of performance issues - the recommended approach is to use a MutationObserver to watch the DOM. It's only supported in newer browsers though, so you should fall back onto DOMNodeInserted when MutationObserver isn't available.

let observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (!mutation.addedNodes) return

    for (let i = 0; i < mutation.addedNodes.length; i++) {
      // do things to your newly added nodes here
      let node = mutation.addedNodes[i]
    }
  })
})

observer.observe(document.body, {
    childList: true
  , subtree: true
  , attributes: false
  , characterData: false
})

// stop watching using:
observer.disconnect()
Lumin
  • 373
  • 6
  • 21
hughsk
  • 3,822
  • 1
  • 21
  • 16
  • 73
    I've always found MutationObserver api a bit complex so I've built a library, [arrive.js](https://github.com/uzairfarooq/arrive), to provide a simpler api to listen for elements creation/removal. – Uzair Farooq Apr 17 '14 at 18:23
  • 22
    I recommend using @UzairFarooq excellent library https://github.com/uzairfarooq/arrive – Dennis Jul 14 '14 at 15:13
  • @hughsk : How I can do it only for a particular `
    `element?
    – user2284570 Oct 12 '14 at 20:01
  • As noted, MutationObservers don't work in IE10 or below. I believe part of Web Components has made a polyfill for IE9 support [here](https://github.com/webcomponents/webcomponentsjs/tree/master/src/MutationObserver). – aug Nov 23 '14 at 09:31
  • 4
    Two things to note: (1) It would be better to do `if (mutation.addedNodes.length)` since `if (mutation.addedNodes)` would still return true even if it's an empty array. (2) You can't do `mutation.addedNodes.forEach()` because addedNodes is a nodeList and you can't iterate through a nodeList with forEach. For a solution to this, see http://toddmotto.com/ditch-the-array-foreach-call-nodelist-hack/ – thdoan Jan 15 '15 at 04:57
  • Just encapsulated answer to jQuery plugin available via Bower. https://github.com/janmisek/jquery.elementReady – Jan Míšek Mar 07 '15 at 11:13
  • @UzairFarooq I tried your library tool Arrive and it is not working at all, I am using Chrome, I wisely followed your github instruction and tried to debug it several times, but it does not work. – Nizar B. Sep 18 '15 at 21:50
  • @Katcha can you provide me jsfiddle of the issue? – Uzair Farooq Sep 19 '15 at 16:27
  • 4
    Can you give an example of how one would use this? Not sure where to put my jquery selector or code I want executed when DOM element exists. – Superdooperhero May 30 '18 at 13:12
  • This is harder than it looks, does anyone have a working algo using MutationObserver in 2019? – SuperUberDuper Jan 10 '19 at 16:30
  • 1
    @Superdooperhero I made an answer with easy example. Check it. https://stackoverflow.com/a/57395241/6542186 – SilverSurfer Aug 07 '19 at 13:10
  • @TalkNerdyToMe this answer doesn't use mutation events, it uses the mutationObserver interface, so it is still very relevant. – Mike Kormendy Aug 20 '20 at 15:01
  • @MikeKormendy You're correct; I have deleted my comment. – Talk Nerdy To Me Aug 20 '20 at 15:08
134

Here is a core JavaScript function to wait for the display of an element (well, its insertion into the DOM to be more accurate).

// Call the below function
waitForElementToDisplay("#div1",function(){alert("Hi");},1000,9000);

function waitForElementToDisplay(selector, callback, checkFrequencyInMs, timeoutInMs) {
  var startTimeInMs = Date.now();
  (function loopSearch() {
    if (document.querySelector(selector) != null) {
      callback();
      return;
    }
    else {
      setTimeout(function () {
        if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs)
          return;
        loopSearch();
      }, checkFrequencyInMs);
    }
  })();
}

This call will look for the HTML tag whose id="div1" every 1000 milliseconds. If the element is found, it will display an alert message Hi. If no element is found after 9000 milliseconds, this function stops its execution.

Parameters:

  1. selector: String : This function looks for the element ${selector}.
  2. callback: Function : This is a function that will be called if the element is found.
  3. checkFrequencyInMs: Number : This function checks whether this element exists every ${checkFrequencyInMs} milliseconds.
  4. timeoutInMs : Number : Optional. This function stops looking for the element after ${timeoutInMs} milliseconds.

NB : Selectors are explained at https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector

Etienne Tonnelier
  • 2,119
  • 1
  • 15
  • 14
  • Nice! Can you write this so that any selector can be accepted? – mattsven Apr 20 '15 at 17:09
  • I doubt I can do it.. But please have a look at this post to get the getElementByXpath: http://stackoverflow.com/questions/10596417/is-there-a-way-to-get-element-by-xpath-in-javascript – Etienne Tonnelier Apr 20 '15 at 17:17
  • 1
    What about `querySelector`? https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector – mattsven Apr 20 '15 at 17:19
  • 1
    Can you write it to use mutation observer instead? – SuperUberDuper Jan 10 '19 at 16:01
  • or could you rewrite this one to use a promise? – SuperUberDuper Jan 10 '19 at 16:03
  • You need to add timeout so it will not be an endless loop if the element was not found. – Gravity API Jul 07 '20 at 16:11
  • @GravityAPI : I added the optional timeout to avoid enless loops. – Etienne Tonnelier Oct 06 '20 at 17:29
  • @SuperUberDuper : hughsk provided an example with the mutation observer, right ? As for a Promise, I will rewrite a new answer with it in the next couple of weeks. Sorry for the long long delay !! – Etienne Tonnelier Oct 06 '20 at 17:35
  • This works in Chrome 2021! I tried 50 different ways to wait for an iFrame url to load in Chrome - nothing worked because I've been told Chrome initiates a blank webpage temporarily whenever the href is changed in an iFrame, so the load functions never fired since it was detecting the blank page already loaded. With this function I can at least wait for the iFrames parent to load. Just need to change the function from "if (document.querySelector(selector) != null) {" to "if (document.getElementById("myFrameIDname").contentDocument.querySelector(selector) != null) {" – John Muggins Jul 28 '21 at 20:50
  • Maybe this was once a good solution, but seems overkill now if you can just observe the DOM. – html_programmer Feb 24 '22 at 22:27
121

I was having this same problem, so I went ahead and wrote a plugin for it.

$(selector).waitUntilExists(function);

Code:

;(function ($, window) {

var intervals = {};
var removeListener = function(selector) {

    if (intervals[selector]) {

        window.clearInterval(intervals[selector]);
        intervals[selector] = null;
    }
};
var found = 'waitUntilExists.found';

/**
 * @function
 * @property {object} jQuery plugin which runs handler function once specified
 *           element is inserted into the DOM
 * @param {function|string} handler 
 *            A function to execute at the time when the element is inserted or 
 *            string "remove" to remove the listener from the given selector
 * @param {bool} shouldRunHandlerOnce 
 *            Optional: if true, handler is unbound after its first invocation
 * @example jQuery(selector).waitUntilExists(function);
 */

$.fn.waitUntilExists = function(handler, shouldRunHandlerOnce, isChild) {

    var selector = this.selector;
    var $this = $(selector);
    var $elements = $this.not(function() { return $(this).data(found); });

    if (handler === 'remove') {

        // Hijack and remove interval immediately if the code requests
        removeListener(selector);
    }
    else {

        // Run the handler on all found elements and mark as found
        $elements.each(handler).data(found, true);

        if (shouldRunHandlerOnce && $this.length) {

            // Element was found, implying the handler already ran for all 
            // matched elements
            removeListener(selector);
        }
        else if (!isChild) {

            // If this is a recurring search or if the target has not yet been 
            // found, create an interval to continue searching for the target
            intervals[selector] = window.setInterval(function () {

                $this.waitUntilExists(handler, shouldRunHandlerOnce, true);
            }, 500);
        }
    }

    return $this;
};

}(jQuery, window));
Roland Soós
  • 3,125
  • 4
  • 36
  • 49
Ryan Lester
  • 2,363
  • 7
  • 25
  • 38
  • 6
    Thank you for the plugin. I forked and improved it a bit. Feel free to take whatever you want from my update. I have a few more improvements planned, still: [updated plugin](https://gist.github.com/PizzaBrandon/5709010) – Brandon Belvin Jun 04 '13 at 21:46
  • 12
    would be nice without jquery dep too... ;) – knutole Jul 25 '13 at 15:30
  • 5
    maybe you should mention how it works: it works by asking every 500 ms if the element exists (using a `window.setInterval`). I don't know if the `MutationObserver` answer works by polling as well... – sports Mar 30 '15 at 21:18
  • 2
    It does not work properly if the element is already on the page. Here is the proper version of this function: https://gist.github.com/PizzaBrandon/5709010 – Roland Soós Feb 24 '16 at 07:09
  • Hey @RolandSoós thanks for the help! Any idea on how this can be used with a class selector with multiple classes possible being selected? `$($('.selector')[0]).waitUntilExists(function)` will not work at the moment, which is my issue. Any ideas or possible fixes? Thanks again! – Kragalon Mar 16 '16 at 21:19
  • @Kragalon think that through... This function checks if an element for that select exists and notify you if it does. If an element is not the but others are there, this won't know that you are waiting for an element on an index... – Roland Soós Mar 18 '16 at 17:27
  • 2
    Can you please explain what is the use of `;` in the beginning of the function ( `;(function ($, window) {` ) ? – mrid Dec 23 '16 at 10:36
  • Yeah, I'd be interested in that too. I thought it had something to do with bad (i.e. non-closed) line-endings, but wasn't sure how that would come about.So I googled it: "It allows you to safely concatenate several JS files into one, to serve it quicker as one HTTP request." https://stackoverflow.com/a/1873999/2848941 – Julix Jul 21 '17 at 20:47
  • 1
    Used to work for me, but does not seem to work anymore in Chrome 66.0.3359.117 – Superdooperhero May 30 '18 at 13:09
  • 1
    @mrid the ; is nowadays used to close the last line of the previous script if that script doesn't have ; at the end, it's a measure for paranoids – Alcides Jan 10 '19 at 20:40
  • @Superdooperhero That's the problem with relying on these self-made plugins. The maintenance is atrocious. – NoName Aug 13 '20 at 15:00
51

I used this approach to wait for an element to appear so I can execute the other functions after that.

Let's say doTheRestOfTheStuff(parameters) function should only be called after the element with ID the_Element_ID appears or finished loading, we can use,

var existCondition = setInterval(function() {
 if ($('#the_Element_ID').length) {
    console.log("Exists!");
    clearInterval(existCondition);
    doTheRestOfTheStuff(parameters);
 }
}, 100); // check every 100ms
prime
  • 14,464
  • 14
  • 99
  • 131
29

Update

Below there is an updated version that works with promises. It also "stops" if a specific number of tries is reached.

function _waitForElement(selector, delay = 50, tries = 100) {
    const element = document.querySelector(selector);

    if (!window[`__${selector}`]) {
      window[`__${selector}`] = 0;
      window[`__${selector}__delay`] = delay;
      window[`__${selector}__tries`] = tries;
    }

    function _search() {
      return new Promise((resolve) => {
        window[`__${selector}`]++;
        setTimeout(resolve, window[`__${selector}__delay`]);
      });
    }

    if (element === null) {
      if (window[`__${selector}`] >= window[`__${selector}__tries`]) {
        window[`__${selector}`] = 0;
        return Promise.resolve(null);
      }

      return _search().then(() => _waitForElement(selector));
    } else {
      return Promise.resolve(element);
    }
  }

Usage is very simple, to use it with await just make sure you're within an async function:

const start = (async () => {
  const $el = await _waitForElement(`.my-selector`);
  console.log($el);
})();

Outdated version

Simply add the selector you want. Once the element is found you can have access to in the callback function.

const waitUntilElementExists = (selector, callback) => {
const el = document.querySelector(selector);

if (el){
    return callback(el);
}

setTimeout(() => waitUntilElementExists(selector, callback), 500);
}

waitUntilElementExists('.wait-for-me', (el) => console.log(el));
Diego Fortes
  • 8,830
  • 3
  • 32
  • 42
  • 3
    PossessWithin agree, this is a very clean solution and works for me. – jstafford Aug 02 '19 at 01:33
  • 3
    This answer works on IE8-10 as well as modern browsers. The main problem is that it will keep running if the element does not exist - so its best when are you are sure the element is going to be there. Otherwise, you could add a counter. – For the Name Aug 17 '19 at 16:54
  • 1
    Worked perfectly for me – James Stewart Sep 04 '19 at 15:48
  • 1
    Worked like charm !! – Aman Oct 11 '19 at 22:39
  • 1
    You were down voted most likely because similar answers exists here and they were posted in 2012 and 2015 e.g. https://stackoverflow.com/a/29754070/2103767 – bhantol Nov 19 '19 at 15:51
  • 2
    They were similar, not identical. Furthermore, many people are doing the same. Lastly, I coded this solution myself. That is a poor reasoning, however, if even it was indeed the case, I'd appreciate a comment letting me know. The answer solves OP's issue and has no apparent motives to be downvoted. – Diego Fortes Nov 19 '19 at 19:26
29

I think that still there isnt any answer here with easy and readable working example. Use MutationObserver interface to detect DOM changes, like this:

var observer = new MutationObserver(function(mutations) {
    if ($("p").length) {
        console.log("Exist, lets do something");
        observer.disconnect(); 
        //We can disconnect observer once the element exist if we dont want observe more changes in the DOM
    }
});

// Start observing
observer.observe(document.body, { //document.body is node target to observe
    childList: true, //This is a must have for the observer with subtree
    subtree: true //Set to true if changes must also be observed in descendants.
});
            
$(document).ready(function() {
    $("button").on("click", function() {
        $("p").remove();
        setTimeout(function() {
            $("#newContent").append("<p>New element</p>");
        }, 2000);
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<button>New content</button>
<div id="newContent"></div>

Note: Spanish Mozilla docs about MutationObserver are more detailed if you want more information.

SilverSurfer
  • 4,281
  • 6
  • 24
  • 50
26

You can do

$('#yourelement').ready(function() {

});

Please note that this will only work if the element is present in the DOM when being requested from the server. If the element is being dynamically added via JavaScript, it will not work and you may need to look at the other answers.

aug
  • 11,138
  • 9
  • 72
  • 93
Splynx
  • 823
  • 2
  • 8
  • 20
  • 9
    The `.ready()` function works for most anything (if not anything), not just `document`. It just won't work with dynamically created elements, even on `.live()`. – Richard Neil Ilagan Apr 02 '11 at 18:47
  • 8
    @Bery, as Richard pointed out, this works only for elements which are already present in the HTML when it's first requested from the server. If Javascript is used to add an element dynamically to the DOM, it doesn't work. – Chandranshu Jan 30 '13 at 12:11
  • It works perfectly. Just attach it to the reference of the element in memory, not to the string '#yourelement' – Sam Oct 30 '13 at 10:51
  • 6
    @Sam, can you please clarify how to attach it to the reference of the element in memory? – Vikas Singhal Feb 19 '14 at 17:48
  • 5
    This answer is incorrect. What you're actually checking here is a regular `$(document).ready()`, not the element you think it will apply too. That's just how this *special* listener works. [Example](http://codepen.io/anon/pen/yeqxdV?editors=0010) – Shikkediel Feb 02 '16 at 18:34
  • 2
    This usage is not recommended according to https://api.jquery.com/ready/ – splintor Jan 10 '18 at 07:37
25

You can listen to DOMNodeInserted or DOMSubtreeModified events which fire whenever a new element is added to the DOM.

There is also LiveQuery jQuery plugin which would detect when a new element is created:

$("#future_element").livequery(function(){
    //element created
});
ElephantHunter
  • 1,552
  • 1
  • 11
  • 15
serg
  • 109,619
  • 77
  • 317
  • 330
  • 1
    Very nice plugin! Is there any function like that in jquery directly? I'm wondering that there is no existing feature to do that. And if this is THE plugin, please vote up for this answer ;) For me, it works perfectly. Thank you very much. – Samuel Aug 10 '12 at 20:35
  • 1
    Note IE 9 implements DOMNodeInserted but has a major bug where it won't fire when you add an element for the time, which is most of the time when you'd want to use it. Details are at: http://help.dottoro.com/ljmcxjla.php – mikemaccana Dec 03 '12 at 11:48
  • `DOMSubtreeModified` is deprecated in favor of the [Mutation Observer API](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) – Anand Rockzz Jan 07 '22 at 03:08
16

For a simple approach using jQuery I've found this to work well:

  // Wait for element to exist.
  function elementLoaded(el, cb) {
    if ($(el).length) {
      // Element is now loaded.
      cb($(el));
    } else {
      // Repeat every 500ms.
      setTimeout(function() {
        elementLoaded(el, cb)
      }, 500);
    }
  };

  elementLoaded('.element-selector', function(el) {
    // Element is ready to use.
    el.click(function() {
      alert("You just clicked a dynamically inserted element");
    });
  });

Here we simply check every 500ms to see whether the element is loaded, when it is, we can use it.

This is especially useful for adding click handlers to elements which have been dynamically added to the document.

Hedley Smith
  • 1,307
  • 15
  • 12
10

How about the insertionQuery library?

insertionQuery uses CSS Animation callbacks attached to the selector(s) specified to run a callback when an element is created. This method allows callbacks to be run whenever an element is created, not just the first time.

From github:

Non-dom-event way to catch nodes showing up. And it uses selectors.

It's not just for wider browser support, It can be better than DOMMutationObserver for certain things.

Why?

  • Because DOM Events slow down the browser and insertionQuery doesn't
  • Because DOM Mutation Observer has less browser support than insertionQuery
  • Because with insertionQuery you can filter DOM changes using selectors without performance overhead!

Widespread support!

IE10+ and mostly anything else (including mobile)

Community
  • 1
  • 1
aLx13
  • 701
  • 5
  • 16
9

Here's a function that acts as a thin wrapper around MutationObserver. The only requirement is that the browser support MutationObserver; there is no dependency on JQuery. Run the snippet below to see a working example.

function waitForMutation(parentNode, isMatchFunc, handlerFunc, observeSubtree, disconnectAfterMatch) {
  var defaultIfUndefined = function(val, defaultVal) {
    return (typeof val === "undefined") ? defaultVal : val;
  };

  observeSubtree = defaultIfUndefined(observeSubtree, false);
  disconnectAfterMatch = defaultIfUndefined(disconnectAfterMatch, false);

  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.addedNodes) {
        for (var i = 0; i < mutation.addedNodes.length; i++) {
          var node = mutation.addedNodes[i];
          if (isMatchFunc(node)) {
            handlerFunc(node);
            if (disconnectAfterMatch) observer.disconnect();
          };
        }
      }
    });
  });

  observer.observe(parentNode, {
    childList: true,
    attributes: false,
    characterData: false,
    subtree: observeSubtree
  });
}

// Example
waitForMutation(
  // parentNode: Root node to observe. If the mutation you're looking for
  // might not occur directly below parentNode, pass 'true' to the
  // observeSubtree parameter.
  document.getElementById("outerContent"),
  // isMatchFunc: Function to identify a match. If it returns true,
  // handlerFunc will run.
  // MutationObserver only fires once per mutation, not once for every node
  // inside the mutation. If the element we're looking for is a child of
  // the newly-added element, we need to use something like
  // node.querySelector() to find it.
  function(node) {
    return node.querySelector(".foo") !== null;
  },
  // handlerFunc: Handler.
  function(node) {
    var elem = document.createElement("div");
    elem.appendChild(document.createTextNode("Added node (" + node.innerText + ")"));
    document.getElementById("log").appendChild(elem);
  },
  // observeSubtree
  true,
  // disconnectAfterMatch: If this is true the hanlerFunc will only run on
  // the first time that isMatchFunc returns true. If it's false, the handler
  // will continue to fire on matches.
  false);

// Set up UI. Using JQuery here for convenience.

$outerContent = $("#outerContent");
$innerContent = $("#innerContent");

$("#addOuter").on("click", function() {
  var newNode = $("<div><span class='foo'>Outer</span></div>");
  $outerContent.append(newNode);
});
$("#addInner").on("click", function() {
  var newNode = $("<div><span class='foo'>Inner</span></div>");
  $innerContent.append(newNode);
});
.content {
  padding: 1em;
  border: solid 1px black;
  overflow-y: auto;
}
#innerContent {
  height: 100px;
}
#outerContent {
  height: 200px;
}
#log {
  font-family: Courier;
  font-size: 10pt;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>Create some mutations</h2>
<div id="main">
  <button id="addOuter">Add outer node</button>
  <button id="addInner">Add inner node</button>
  <div class="content" id="outerContent">
    <div class="content" id="innerContent"></div>
  </div>
</div>
<h2>Log</h2>
<div id="log"></div>
Ivan Karajas
  • 1,081
  • 8
  • 14
8

You can try this:

const wait_until_element_appear = setInterval(() => {
    if ($(element).length !== 0) {
        // some code
        clearInterval(wait_until_element_appear);
    }
}, 0);

This solution works very good for me

Heiren Awx
  • 81
  • 1
  • 3
  • 2
    Clean and concise. You may want to increase the interval ie. to 500ms, and maybe add a retry counter to avoid infinite loop. – m3nda Jul 05 '21 at 10:33
7

Here's a Promise-returning solution in vanilla Javascript (no messy callbacks). By default it checks every 200ms.

function waitFor(selector) {
    return new Promise(function (res, rej) {
        waitForElementToDisplay(selector, 200);
        function waitForElementToDisplay(selector, time) {
            if (document.querySelector(selector) != null) {
                res(document.querySelector(selector));
            }
            else {
                setTimeout(function () {
                    waitForElementToDisplay(selector, time);
                }, time);
            }
        }
    });
}
blaster
  • 8,876
  • 11
  • 48
  • 77
7

The observe function below will allow you to listen to elements via a selector.

In the following example, after 2 seconds have passed, a .greeting will be inserted into the .container. Since we are listening to the insertion of this element, we can have a callback that triggers upon insertion.

const observe = (selector, callback, targetNode = document.body) =>
  new MutationObserver(mutations => [...mutations]
    .flatMap((mutation) => [...mutation.addedNodes])
    .filter((node) => node.matches && node.matches(selector))
    .forEach(callback))
  .observe(targetNode, { childList: true, subtree: true });

const createGreeting = () => {
  const el = document.createElement('DIV');
  el.textContent = 'Hello World';
  el.classList.add('greeting');
  return el;
};

const container = document.querySelector('.container');

observe('.greeting', el => console.log('I have arrived!', el), container);

new Promise(res => setTimeout(() => res(createGreeting()), 2000))
  .then(el => container.appendChild(el));
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
body { display: flex; }
.container { display: flex; flex: 1; align-items: center; justify-content: center; }
.greeting { font-weight: bold; font-size: 2em; }
<div class="container"></div>

Update

Here is an experimental async/await example.

const sleep = (ms) => new Promise((res) => setTimeout(res, ms));

const observe = (selector, targetNode = document.body) =>
  new Promise(res => {
    new MutationObserver(mutations =>
      res([...mutations]
        .flatMap((mutation) => [...mutation.addedNodes])
        .find((node) => node.matches && node.matches(selector))))
    .observe(targetNode, { childList: true, subtree: true });
  });

const createGreeting = () => {
  const el = document.createElement('DIV');
  el.textContent = 'Hello World';
  el.classList.add('greeting');
  return el;
};

const container = document.querySelector('.container');

observe('.greeting', container)
  .then(el => console.log('I have arrived!', el));

(async () => {
  await sleep(2000);
  container.appendChild(createGreeting());
})();
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
body { display: flex; }
.container { display: flex; flex: 1; align-items: center; justify-content: center; }
.greeting { font-weight: bold; font-size: 2em; }
<div class="container"></div>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
6

Here's a pure Javascript function which allows you to wait for anything. Set the interval longer to take less CPU resource.

/**
 * @brief Wait for something to be ready before triggering a timeout
 * @param {callback} isready Function which returns true when the thing we're waiting for has happened
 * @param {callback} success Function to call when the thing is ready
 * @param {callback} error Function to call if we time out before the event becomes ready
 * @param {int} count Number of times to retry the timeout (default 300 or 6s)
 * @param {int} interval Number of milliseconds to wait between attempts (default 20ms)
 */
function waitUntil(isready, success, error, count, interval){
    if (count === undefined) {
        count = 300;
    }
    if (interval === undefined) {
        interval = 20;
    }
    if (isready()) {
        success();
        return;
    }
    // The call back isn't ready. We need to wait for it
    setTimeout(function(){
        if (!count) {
            // We have run out of retries
            if (error !== undefined) {
                error();
            }
        } else {
            // Try again
            waitUntil(isready, success, error, count -1, interval);
        }
    }, interval);
}

To call this, for example in jQuery, use something like:

waitUntil(function(){
    return $('#myelement').length > 0;
}, function(){
    alert("myelement now exists");
}, function(){
    alert("I'm bored. I give up.");
});
xgretsch
  • 1,294
  • 13
  • 15
5

I usually use this snippet for Tag Manager:

<script>
(function exists() {
  if (!document.querySelector('<selector>')) {
    return setTimeout(exists);
  }
  // code when element exists
})();  
</script>
Alejo JM
  • 921
  • 7
  • 12
3

A solution returning a Promise and allowing to use a timeout (compatible IE 11+).

For a single element (type Element):

"use strict";

function waitUntilElementLoaded(selector) {
    var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

    var start = performance.now();
    var now = 0;

    return new Promise(function (resolve, reject) {
        var interval = setInterval(function () {
            var element = document.querySelector(selector);

            if (element instanceof Element) {
                clearInterval(interval);

                resolve();
            }

            now = performance.now();

            if (now - start >= timeout) {
                reject("Could not find the element " + selector + " within " + timeout + " ms");
            }
        }, 100);
    });
}

For multiple elements (type NodeList):

"use strict";

function waitUntilElementsLoaded(selector) {
    var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;

    var start = performance.now();
    var now = 0;

    return new Promise(function (resolve, reject) {
        var interval = setInterval(function () {
            var elements = document.querySelectorAll(selector);

            if (elements instanceof NodeList) {
                clearInterval(interval);

                resolve(elements);
            }

            now = performance.now();

            if (now - start >= timeout) {
                reject("Could not find elements " + selector + " within " + timeout + " ms");
            }
        }, 100);
    });
}

Examples:

waitUntilElementLoaded('#message', 800).then(function(element) {
    // element found and available

    element.innerHTML = '...';
}).catch(function() {
    // element not found within 800 milliseconds
});

waitUntilElementsLoaded('.message', 10000).then(function(elements) {
    for(const element of elements) {
        // ....
    }
}).catch(function(error) {
    // elements not found withing 10 seconds
});

Works for both a list of elements and a single element.

Neithan Max
  • 11,004
  • 5
  • 40
  • 58
Anwar
  • 4,162
  • 4
  • 41
  • 62
  • 1
    My favorite solution! Why check `element instanceof HTMLElement`? Can it ever be anything other than `null` or `HTMLElement`? – Leeroy Nov 14 '19 at 17:59
  • 1
    You raise an interesting point. I should have make it broader by using `Element` instead (fixed). I just make the check because I want to be sure the variable `element` has the property `innerHTML` as the [Element MDN documentation states](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML). Feel free to remove it if you do not care about it! – Anwar Nov 14 '19 at 22:14
3

I have developed an answer inspired by Jamie Hutber's.

It's a promise based function where you can set:

  • maximum number of tries - default 10;
  • delay in milliseconds - default 100 ms.

Therefore, by default, it will wait 1 second until the element appears on the DOM.

If it does not show up it will return a promise.reject with null so you can handle the error as per your wish.

Code

export function _waitForElement(selector, delay = 10, tries = 100) {
  const element = document.querySelector(selector);


  if (!window[`__${selector}`]) {
    window[`__${selector}`] = 0;
    window[`__${selector}__delay`] = delay;
    window[`__${selector}__tries`] = tries;
  }

  function _search() {
    return new Promise((resolve) => {
      window[`__${selector}`]++;
      setTimeout(resolve, window[`__${selector}__delay`]);
    });
  }

  if (element === null) {
    if (window[`__${selector}`] >= window[`__${selector}__tries`]) {
      window[`__${selector}`] = 0;
      return Promise.resolve(null);
    }

    return _search().then(() => _waitForElement(selector));
  } else {
    return Promise.resolve(element);
  }
}

Usage:

async function wait(){
    try{
        const $el = await waitForElement(".llama");
        console.log($el);
    } catch(err){
        console.error("Timeout - couldn't find element.")
    }
} 

wait();

In the example above it will wait for the selector .llama. You can add a greater delay and test it here on the console of StackoverFlow.

Just add the class llama to any element on the DOM.

Diego Fortes
  • 8,830
  • 3
  • 32
  • 42
2

A cleaner example using MutationObserver:

new MutationObserver( mutation => {
    if (!mutation.addedNodes) return
    mutation.addedNodes.forEach( node => {
        // do stuff with node
    })
})
Zaz
  • 46,476
  • 14
  • 84
  • 101
2

This is a better version written on top of Yong Wang's answer (highest scored answer).

Added feature: you can wait for an element for a particular amount of time with location precision to increase performance.

async function waitForElement(selector, timeout = null, location = document.body) {
    return new Promise((resolve) => {
        let element = document.querySelector(selector);
        if (element) {
            return resolve(element);
        }

        const observer = new MutationObserver(async () => {
            let element = document.querySelector(selector);
            if (element) {
                resolve(element);
                observer.disconnect();
            } else {
                if (timeout) {
                    async function timeOver() {
                        return new Promise((resolve) => {
                            setTimeout(() => {
                                observer.disconnect();
                                resolve(false);
                            }, timeout);
                        });
                    }
                    resolve(await timeOver());
                }
            }
        });

        observer.observe(location, {
            childList: true,
            subtree: true,
        });
    });
}

Usage:

await waitForElement(".nav-alt", 500, ".main-body")

Bonus: Wait for a element to disappear from DOM.

async function waitForElementDeath(selector, location = document.body) {
    return new Promise((resolve) => {
        const observer = new MutationObserver(async () => {
            if (!document.querySelector(selector)) {
                resolve(true);
                observer.disconnect();
            }
        });

        observer.observe(location, {
            childList: true,
            subtree: true,
        });
    });
}

Usage:

await waitForElementDeath(".Popup-div", "Popup-Container")
  • 1
    Nice answer! Small suggestion: it looks like sometimes you're doing `querySelector()` twice sometimes when you don't need to be (like when your if condition has already verified the element exists) – mattsven Nov 01 '22 at 09:17
  • in this solution for "Usage" how do you put a callback? – Kevin Chavez Mar 17 '23 at 06:49
  • I didn't use callbacks i just use `await waitForElement` inside a funtion and it will wait until the element is found and executes the rest. But you can add a [callback fuction](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) just below `resolve(true)` or `resolve(element)` by creating a parameter in `waitForElement` funtion – Raghavan Vidhyasagar Mar 24 '23 at 13:04
  • @mattsven I have used `querySelector()` just once, it might be specified in two placed but the second one is inside a callback function of `MutationObserver`. so it will call it again and again whenever there is a Mutation – Raghavan Vidhyasagar Mar 24 '23 at 13:09
  • @RaghavanVidhyasagar My comment was in response to an earlier version of your answer – mattsven Mar 24 '23 at 17:10
2

Instead of querySelector you can also use getElementById

This function is exactly same as https://stackoverflow.com/a/61511955/10798137

  async function waitForElementById(id, timeout = null, location = document.body) {
    return new Promise((resolve) => {
        let element = document.getElementById(id);
        if (element) {
            return resolve(element);
        }

        const observer = new MutationObserver(async () => {
            let element = document.getElementById(id);
            if (element) {
                resolve(element);
                observer.disconnect();
            } else {
                if (timeout) {
                    async function timeOver() {
                        return new Promise((resolve) => {
                            setTimeout(() => {
                                observer.disconnect();
                                resolve(false);
                            }, timeout);
                        });
                    }
                    resolve(await timeOver());
                }
            }
        });

        observer.observe(location, {
            childList: true,
            subtree: true,
        });
    });
}

To use it


    waitForElementById("tag_id", 500).then((elm) => {
      console.log(elm)
    })

Or


    var elm = async waitForElementById("tag_id", 500)

Paulina Khew
  • 397
  • 4
  • 13
Sanaf
  • 336
  • 3
  • 13
1

If you want it to stop looking after a while (timeout) then the following jQuery will work. It will time out after 10sec. I needed to use this code rather than pure JS because I needed to select an input via name and was having trouble implementing some of the other solutions.

 // Wait for element to exist.

    function imageLoaded(el, cb,time) {

        if ($(el).length) {
            // Element is now loaded.

            cb($(el));

            var imageInput =  $('input[name=product\\[image_location\\]]');
            console.log(imageInput);

        } else if(time < 10000) {
            // Repeat every 500ms.
            setTimeout(function() {
               time = time+500;

                imageLoaded(el, cb, time)
            }, 500);
        }
    };

    var time = 500;

    imageLoaded('input[name=product\\[image_location\\]]', function(el) {

     //do stuff here 

     },time);
S-Thomas
  • 41
  • 4
1

I try to avoid mutation observers if I can help it, so this is what I came up with. It looks similar to some of the other answers above. This function will look for the first element to exist within a given DOM call -- className being the expected usage but it can also accept tagName or Id. You could also add an argument for a precise index if you were looking for some number of elements with a given classname or tagname to have loaded.

    async function waitUntilElementExits(domkey,domquery,maxtime){
        const delay = (ms) => new Promise(res => setTimeout(res, ms));
        for(let i=0; i<maxtime; i=i+200){
            await delay(200);
            let elm = document[domkey](domquery);
            if( (domkey == 'getElementById' && elm) || elm?.[0] ) break;
        }
    }
    // usage
    await waitUntilElementExits('getElementByClassName','some_class_name',10000)
Andre Bradshaw
  • 119
  • 1
  • 5
  • 3
    "I try to avoid mutation observers" Why? – mattsven May 14 '22 at 06:06
  • 5
    It's mostly a matter of preference due to the type of work I perform, but in this particular case, I am not sure it makes sense to listen to the entire DOM tree until an element comes into existence. I am typically building web scrapers and addons, and I find that mutation observers are not reliable when I am not confident about the page behavior. This is why I have a maxtime in my solution. I often need my functions to be "try to do this, but give up if its hard." – Andre Bradshaw May 15 '22 at 12:10
1

My take on @Yong Wong's solution, but it has an optional timeout and you can specify the root node from where you'd like to wait for the element.

Full async/await.

const $ = (selector, opts) => {
  let timeout = undefined;
  let root = undefined;

  if (opts) {
    ({ root, timeout } = opts);
  }

  if (root === undefined) root = document.body;
  
  const nodeFound = root.querySelector(selector);
  if (nodeFound) return new Promise(resolve => resolve(nodeFound));

  return new Promise((resolve, reject) => {
    let callback = () => {
      observer.disconnect();
    };

    const _resolve = (node) => {
      callback();
      resolve(node);
    };

    const _reject = (err) => {
      callback();
      reject(err);
    };

    if (timeout && timeout > 0) {
      const handle = setTimeout(() => {
        _reject(new Error("Element not found: timeout exceeded."));
      }, timeout);
      callback = () => {
        observer.disconnect();
        clearTimeout(handle);
      };
    }

    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        for (const addedNode of mutation.addedNodes) {
          if (addedNode.matches(selector)) {
            _resolve(addedNode);
            return;
          }
        }
      }
    });

    observer.observe(root, {
      childList: true,
      subtree: true,
    });
  });
}

Example call:

// wait for 10 seconds for 'div.bla-bla-bla' to appear as a child of 'div.some-container'
await $("div.bla-bla-bla", {
  timeout: 10000,
  root: document.querySelector("div.some-container") 
});
winwin
  • 958
  • 7
  • 25
0

Simple Javascript.

cont elementExist = setInterval(() => {
    var elm = document.getElementById("elementId")
    if (elm!=null)
         // call your function here to do something
        clearInterval(elementExist);
    }
}, 100);

Note: This will block other executions

0

Here is a TypeScript version of Yong Wang's accepted answer using MutationObserver which takes an optional return type that extends from HTMLElement.

This is useful if you need to access element type specific properties (like src on an <img> or href on a <a> link):

function waitFor<T extends HTMLElement>(selector: string): Promise<T> {
  return new Promise((resolve) => {
    const elm = document.querySelector<T>(selector)
    if (elm) return resolve(elm)

    const observer = new MutationObserver((mutations) => {
      const elm = document.querySelector<T>(selector)
      if (elm) {
        resolve(elm)
        observer.disconnect()
      }
    })

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    })
  })
}

Usage:

// By default, returns the type "HTMLElement"
const elm = await waitFor('h1')

// Or specify element type if you know it:
const elm = await waitFor<HTMLFormElement>('form')
Dana Woodman
  • 4,148
  • 1
  • 38
  • 35