37

Is there a way in Javascript or jQuery to find out when a HTML element has been added to the DOM dynamically, either through jQuery.append() or one of the native Javascript's node manipulators? There are probably several different ways to add HTML elements to the page dynamically, so I'm not sure what event or events I should be listening on.

The specific example is that that an anchor tag is being added to the page by a third-party Javascript code (that I cannot modify or easily glean). I want to change the link's text.

I am hoping there is something better than a setTimeout() loop on $(SOME ELEMENT).length > 0. (part of which I saw in How can I determine if a dynamically-created DOM element has been added to the DOM? but it's from 2008)

Stephen
  • 2,410
  • 3
  • 33
  • 57
  • 2
    There are no good ways to do this. The setTimeout version works everywhere, but polling the DOM usually is'nt something you should do. Mutation events work in some browsers, but are already deprecated, and probably should'nt be used. – adeneo May 17 '13 at 22:40
  • What event is the third party tool using to trigger the adding of the link? Is it on page load or on the click of some element on the page? – Blake Plumb May 17 '13 at 22:44
  • Blake, I can't easily tell. The code is minified and spread over several JS files, so I can't set a breakpoint easily. – Stephen May 18 '13 at 14:06
  • adeneo, yeah, I wanted to use Mutation Events when I read about them over at the Mozilla documentation, but I found out they were deprecated. – Stephen May 18 '13 at 15:01
  • 1
    possible duplicate of [Event when element added to page](http://stackoverflow.com/questions/7434685/event-when-element-added-to-page) – Dan Dascalescu Jul 15 '15 at 02:34

4 Answers4

32

You can use Mutation Observers for this purpose - at least if you do not need to support IE/Opera.

Here's a short example (taken from html5rocks.com) on how they are used:

var insertedNodes = [];
var observer = new WebKitMutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        for(var i = 0; i < mutation.addedNodes.length; i++)
            insertedNodes.push(mutation.addedNodes[i]);
    })
});
observer.observe(document, {
    childList: true
});
console.log(insertedNodes);

Note the WebKit prefix. You need to use the browser-specific prefix. In Firefox it would be Moz instead.

2022 Update: You don't need to use WebKit nor Moz as a preifx, as the majority of the browsers have implemented it. It is now accessible with MutationObserver.

Roj
  • 995
  • 1
  • 8
  • 22
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • 2
    Nice answer! IE finds a way to cause problems with everything :) – tymeJV May 17 '13 at 22:59
  • 2
    This appears to be the best answer. I would like to be able to support IE8+, but I think I can advocate a compromise, where IE gets the default text the third-party JS serves while other browsers that support the Observers can use this. – Stephen May 18 '13 at 15:04
  • 1
    FYI, I just posted a solution that can handle IE10+. See my answer. – naugtur Feb 06 '14 at 10:51
  • @naugtur your answer is deleted currently. (In it you should have probably added a description of how it works, instead of how it _doesn't_ work, and [a link](https://github.com/naugtur/insertionQuery).) Using animation events for this seems like a nice idea, but on the downside i guess it wouldn't work for hidden (`display: none`) elements. Is it noticeably faster than Mutation Observer? – Qtax Apr 08 '15 at 15:55
  • I know why it got deleted, it wasn't the only one. I had trouble finding it and lost motivation. Now I edited and flagged it so it might come back. Anyway, it's less responsive than Mutation Observer (delay in new element detection I measured was up to 40ms) but in some cases it can be less of a burden for the website performance. If you don't need to match selectors, MO is always the best choice. – naugtur Apr 09 '15 at 20:23
  • 2
    Don't forget to explicitly `disconnect()` the observer when you are done, otherwise it will keep calling the callback for the whole lifetime of the `document`, long after the nodes you actually wanted to observe have been garbage collected. – GOTO 0 Jun 21 '18 at 17:46
  • 3
    Warning: this solution will not work if your element is added to the page within the subtree of some other element. It must be the rootmost element being appended. – Seph Reed Jan 05 '21 at 22:50
  • It seems that in FireFox, it's just MutationObserver, not WebKitMutationObserver. – Damn Vegetables Feb 21 '21 at 03:28
18

Based on the technique by Daniel Buchner I have created a small library to catch DOM insertions. It's more comfortable to use than the hack itself.

It uses css animation events, so it's not based on DOMMutationEvents, and it's not Mutation Observer. So it's not slowing down the page and it has a wider support than MutationObserver.

It has an added benefit of being able to use any CSS selectors you want.

Example usage:

insertionQ('.content div').every(function(element){
    //callback on every new div element inside .content
});

If you invoke it after DOMReady, it's going to catch only new elements.

See https://github.com/naugtur/insertionQuery for more options

naugtur
  • 16,827
  • 5
  • 70
  • 113
  • This worked great to hide/show the Intercom widget programmatically (they don't provide an API for that). – Dan Dascalescu Jul 15 '15 at 12:38
  • Another library that incporporates the same technique is *better-dom* providing [Live Extensions](https://github.com/chemerisuk/better-dom/wiki/Live-extensions). – feeela Nov 15 '17 at 12:46
9

Why don't you try this? I dont remember the exact function I used but it's kinda similar than this...

$(document).bind('DOMNodeInserted', function(event) {
      alert('inserted ' + event.target.nodeName + // new node
            ' in ' + event.relatedNode.nodeName); // parent
});

Also, I found some other options at this link:

Event when element added to page

Community
  • 1
  • 1
jpganz18
  • 5,508
  • 17
  • 66
  • 115
  • 8
    It is deprecated because it's slow and generally a bad idea, but this answer remains a valid solution. – naugtur Feb 06 '14 at 10:47
4

Not sure if anyone is still looking for this but I was and this is the solution I ended up using.

var __css = document.createElement("style");
__css.type = "text/css";
__css.innerHTML = '.alertInsertion {animation: __nodeInserted 0.001s !important; -o-animation: __nodeInserted 0.001s !important; -ms-animation: __nodeInserted 0.001s !important; -moz-animation: __nodeInserted 0.001s !important; -webkit-animation: __nodeInserted 0.001s !important;}'+
'@keyframes __nodeInserted {from{outline-color: #111;}to{outline-color: #000;}}'+
'@-moz-keyframes __nodeInserted {from{outline-color: #111;}to{outline-color: #000;}}'+
'@-webkit-keyframes __nodeInserted {from{outline-color: #111;}to{outline-color: #000;}}'+
'@-ms-keyframes __nodeInserted {from{outline-color: #111;}to{outline-color: #000;}}'+
'@-o-keyframes __nodeInserted {from{outline-color: #111;}to{outline-color: #000;}}';
document.body.appendChild(__css);

insertion_event = function(event){
    if (event.animationName == '__nodeInserted') {
        event.target.className = event.target.className.replace(/\balertInsertion\b/,'');
        document.dispatchEvent(new CustomEvent('elementInserted', {'target': event.target}));
    }
}

document.addEventListener('animationstart', insertion_event, false);
document.addEventListener('MSAnimationStart', insertion_event, false);
document.addEventListener('webkitAnimationStart', insertion_event, false);

Adding the class 'alertInsertion' to the css of the page with a call to the __nodeInserted animation.

if the animation is triggered it removes the class from the element and sends out a custom event called elementInserted that includes the element that triggered it.

simply use it by adding an event listener:

document.addEventListener('elementInserted', function(e)
{
    //Do whatever you want with e.target here.
});

and adding the .alertInsertion class to any elements you want to trigger it.

If the element got another class with an animation it will run after this class has been removed.

ScumbagNiad
  • 210
  • 3
  • 8