30

DOMNodeInserted is known to make dynamic pages slow, MDN even recommends not using it altogether, but doesn't provide any alternatives.

I'm not interested in the element inserted, I just need to know when some script modifies the DOM. Is there a better alternative to mutation event listeners (maybe getElementsByTagName inside an nsiTimer)?

000
  • 26,951
  • 10
  • 71
  • 101
Fábio
  • 3,291
  • 5
  • 36
  • 49

6 Answers6

43

If you are creating a web app that targets recent mobile phones and newer versions of browsers (Firefox 5+, Chrome 4+, Safari 4+, iOS Safari 3+, Android 2.1+), you can use the following code to create an awesome event for the insertion of dom nodes, and it even runs on the nodes initially part of the page's static mark-up!

Here's the link to the full post with and example: http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/

Note on Mutation Observers: while the newer Mutation Observers features in recent browsers are great for monitoring simple insertions and changes to the DOM, do understand that this method can be used to do far more as it allows you to monitor for any CSS rule match you can thing of. This is super powerful for many use-cases, so I wrapped this up in a library here: https://github.com/csuwildcat/SelectorListener

You'll need to add the appropriate prefixes to the CSS and animationstart event name if you want to target various browsers. You can read more about that in the post linked to above.

The basic node insertion case

CSS:

@keyframes nodeInserted {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    }  
}

div.some-control {
    animation-duration: 0.01s;
    animation-name: nodeInserted;
}

JavaScript:

document.addEventListener('animationstart', function(event){
    if (event.animationName == 'nodeInserted'){
        // Do something here
    }
}, true);

Listening for more complex selector matches:

This enables things that are almost impossible to do with Mutation Observers

CSS:

@keyframes adjacentFocusSequence {  
    from {  
        outline-color: #fff; 
    }
    to {  
        outline-color: #000;
    }  
}

.one + .two + .three:focus {
    animation-duration: 0.01s;
    animation-name: adjacentFocusSequence;
}

JavaScript:

document.addEventListener('animationstart', function(event){
    if (event.animationName == 'adjacentFocusSequence'){
        // Do something here when '.one + .two + .three' are 
        // adjacent siblings AND node '.three' is focused
    }
}, true);
csuwldcat
  • 8,021
  • 2
  • 37
  • 32
  • 5
    This is by far the most excellent alternative to `DOMNodeInserted` I've ever seen. In fact I have been using it for months and it works well. – OpenGG Aug 16 '12 at 13:55
  • The technique can be applied to IE10+ but `clip: rect` doesn't work in IE10 (not sure about 11). I used `outline` in my `insertionQuery` lib https://github.com/naugtur/insertionQuery – naugtur Feb 03 '14 at 10:07
  • @naugtur outline is a good idea, especially if you can do the dummy animation on just the color. I'll probably update this thread and my blog post with that. – csuwldcat Mar 04 '14 at 18:06
  • @naugtur it's now updated on Github, and I've tweaked this answer to match - thanks for the heads-up! – csuwldcat Mar 04 '14 at 18:23
  • 2
    Sorry to say that, but now you're just confusing people. The code in your post is far from being cross-browser (animation events have prefixes, animation itself needs other prefixes) and I never said you can animate just the color. It's not what I tested. – naugtur Mar 05 '14 at 19:23
  • @csuwldcat I'm really sorry you got offended. I wasn't scolding anyone, I'll respond more carefully in the future. Your first response to my comment looked to me like a misunderstanding. Tweaking the So answer to be more cross-browser while it didn't mention prefixes is confusing to some readers. A reminder 'use prefixes for animation event' would suffice. – naugtur Mar 07 '14 at 21:50
  • @naugtur I guess this may be a misunderstanding given your last comment. My point was, that I had already included in the answer the _exact_ reminder you are talking about --> _"You'll need to add the aproprieate prefixes to the CSS and animationstart event name if you want to target various browsers. You can read more about that in the post linked to above."_ It has been located just above the first code block since the beginning ;) – csuwldcat Mar 07 '14 at 21:58
  • The only downside is that element needs to be visible on the page in order for browser to trigger animation for it. If you insert anything that is hidden, nothing will happen - of course, not even when you show it afterwards. – Leo Jun 25 '14 at 15:26
  • @Leo this is true, and I was aware of it. It's a small limitation, but for the ability to observe the match of any style rule, it's a small nit. – csuwldcat Jun 26 '14 at 23:40
  • @csuwldcat I agree, it's a great trick. However, we have a very dynamic UI and I was hoping on using this approach to automatically trigger code that replaces selects with a richer widget, adds tooltips to elements etc., as soon as they are added into the DOM. That way, we don't need to take care of calling the method that does this from different places in the code where elements are added...But we do have panels with such elements which are shown on user action, so it's a deal breaker, unfortunately... – Leo Jun 27 '14 at 00:25
  • For your use, I would checkout X-Tag (http://x-tags.org/) or Polymer (http://www.polymer-project.org/) - Web Components do exactly what you want. – csuwldcat Jun 27 '14 at 18:41
  • Excellent demo of the simplicity of your lib's implementation. Native DOM would be more verbose (and less compliant), but all the examples (including @Leo's) can be achieved with `MutationObservers` and [`Element.matches( cssSelector )`](https://developer.mozilla.org/en-US/docs/Web/API/Element.matches), AFAICT. – Barney Dec 16 '14 at 17:10
  • @Barney not true: Mutation Observers don't catch things like focus, so you would need to create a much longer, more complex bit of code, that included both a logic-heavy Observer and a focus event listener, to do the equivalent of what I show in the demo. If you look on GitHub at SelectorListeners, it delivers you this unlimited flexibility in a single-line of code. – csuwldcat Dec 17 '14 at 12:09
  • @Barney remember, CSS rules encompass not only mutations and DOM sequencing, but many other node states that are infeasible or impossible to detect without listening for a rule match ;) – csuwldcat Dec 17 '14 at 12:16
  • It's an excellent library — the DOM API would need a lot of forking to achieve the same thing. Good catch on the pseudo-class selectors! – Barney Dec 17 '14 at 12:33
  • BTW, not 100% certain on this but I think one thing your library can detect but the native DOM API can't is generated content. – Barney Dec 20 '14 at 13:54
11

One new alternative that @naugtur briefly mentioned is MutationObserver. It's designed as a replacement for the deprecated mutation events, if the browser(s) you're developing for supports it (like if you're developing a browser extension).

Mike Henry
  • 2,401
  • 1
  • 25
  • 34
  • 1
    However, Mutation Observers do not provide notification of CSS selector-based mutations though, which is a super helpful feature. I have a small module that wraps the keyframe solution above to make CSS selector listeniong even easier: https://github.com/csuwildcat/SelectorListener – csuwldcat Oct 10 '14 at 20:42
  • Think @csuwldcat's advice is outdated – Mutation Observers can detect [any change](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationObserverInit) you might infer from a CSS selector (using `attributes`), and more (node removal, text node changes). – Barney Dec 15 '14 at 12:21
  • @Barney a couple of things: 1) you can listen for *selector* matches with this method, which is impossible with Mutation Observers - for more on what this provides see: https://github.com/csuwildcat/SelectorListener, and 2) Mutation Observers aren't supported in older browsers like IE9, Safari 5, 6, etc. – csuwldcat Dec 16 '14 at 16:44
  • @Barney I've updated my answer with another use-case that makes it clear what this method can do easily that Mutation Observers cannot. – csuwldcat Dec 16 '14 at 16:59
0

The same technique as csuwldcat described has been made into an easy to use jQuery plugin (if that's your thing): https://github.com/liamdanger/jQuery.DOMNodeAppear

Arktype
  • 95
  • 1
  • 6
0

try to use customElements, but not for already created elements!

customElements.define( 'insertion-triggerable', class extends HTMLElement {
    connectedCallback() {
        console.log( 'connected' )
    }

    disconnectedCallback() {
        console.log( 'disconnected' )
    }

    adoptedCallback() {
        console.log( 'adopted' )
    }
} );

let element = document.createElement( 'insertion-triggerable' );

OR

customElements.define( 'insertion-triggerable', class extends HTMLDivElement {
    // --||--
}, { extends: 'div' } );

let divElement = document.createElement( 'div', { is: 'insertion-triggerable' } );
-1

This is posted here because this question is where I landed looking for help with DOMNodeInserted failing with IE9, but please note this solution is specifically for a situation where jQuery is being used within an ASP.NET context that uses an End Request function. Your mileage may vary, etc....

Basically, we are going to throw away the DOMNodeInserted altogether and use End Request to load our event handler:

OLD:

$(document).ready(function() {

$(document).bind('DOMNodeInserted', function(event){
My jQuery event handler...

 }); 
});

===================================

NEW:

function Ajax_EndRequest {
  function2();
}

function2 (a,b){
  My jQuery event handler...
}

$(document).ready(function(){
  add_endRequest(Ajax_EndRequest); //this is what actually invokes the function upon request end.

 My jQuery event handler...//REMOVED -- don't need this now
});
ColdSharper
  • 74
  • 11
-22

If all you want to do is trigger an event when the DOM changes, do something like this:

var nodes=document.getElementsByTagName('*')||document.all;

function domchange(){
    alert('Hello');
}

window.setInterval(function(){
    var newnodes=document.getElementsByTagName('*')||document.all;
    if(newnodes!=nodes){
        nodes=newnodes;
        domchange();
    }
},1);
Anonymous
  • 21
  • 3
  • 26
    This is terrible code that will massacre any page it is inserted into. –  Aug 17 '11 at 00:41
  • 1
    Not true, but great comment. You can change that last little "1" to 500 if you want. That would update every half second instead of (up to) 1000 times per second. – Anonymous Aug 17 '11 at 00:42
  • 13
    Go to a big site like msnbc.com and note how many big the `getElementsByTagName("*")` collection is. I got "2605" elements. You can't seriously think this is a good idea. –  Aug 17 '11 at 00:57
  • Hahaha, I just had to log in to applaud you guys ... these comments are pure gold, indeed. – aefxx Sep 23 '16 at 15:04
  • Yeah the `*` operator in CSS alone is expensive. Please don't add `alert(...)` too. And the 1ms poll omg! – Matt W Sep 17 '20 at 08:20