Alternative to DOMNodeInserted
Asked Answered
P

6

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)?

Pierpont answered 9/8, 2011 at 14:30 Comment(8)
You need that feature for the entire DOM? And at all times?Length
If you have a particular script and you know how it works - you can "booby-trap" the DOM methods/properties it uses. Otherwise you are out of luck, monitoring all DOM modifications is exactly what makes mutation events slow.Tiffa
Šime Vidas, yes I do, some scripts insert <object> tags when the user interacts with the page.Gitt
@Fabio Are those scripts third-party or your scripts?Length
Third-party. I have an extension that keeps track of content in webpages.Gitt
So are you interested in object or the entire DOM?Dymoke
I'm interested in knowing when my DOM changes so I can take apropriate action. In my case I'm interested in media elements inserted in the page (<embed>, <object>, <video>).Gitt
@Pierpont checkout the new answer I added, I believe you'll like it ;)Plumbo
P
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);
Plumbo answered 27/4, 2012 at 2:12 Comment(16)
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.Diabolo
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 github.com/naugtur/insertionQueryAward
@Award 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.Plumbo
@Award it's now updated on Github, and I've tweaked this answer to match - thanks for the heads-up!Plumbo
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.Award
@Plumbo 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.Award
@Award 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 ;)Plumbo
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.Threescore
@Threescore 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.Plumbo
@Plumbo 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...Threescore
For your use, I would checkout X-Tag (x-tags.org) or Polymer (polymer-project.org) - Web Components do exactly what you want.Plumbo
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 ), AFAICT.Advertent
@Advertent 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.Plumbo
@Advertent 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 ;)Plumbo
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!Advertent
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.Advertent
B
12

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).

Bostow answered 29/6, 2013 at 0:53 Comment(4)
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: github.com/csuwildcat/SelectorListenerPlumbo
Think @csuwldcat's advice is outdated – Mutation Observers can detect any change you might infer from a CSS selector (using attributes), and more (node removal, text node changes).Advertent
@Advertent 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: github.com/csuwildcat/SelectorListener, and 2) Mutation Observers aren't supported in older browsers like IE9, Safari 5, 6, etc.Plumbo
@Advertent I've updated my answer with another use-case that makes it clear what this method can do easily that Mutation Observers cannot.Plumbo
C
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

Combine answered 2/12, 2014 at 0:26 Comment(0)
B
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' } );
Bloodshed answered 2/2, 2022 at 21:51 Comment(0)
Z
-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
});
Zannini answered 14/1, 2013 at 22:45 Comment(0)
O
-23

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);
Oly answered 16/8, 2011 at 5:39 Comment(5)
This is terrible code that will massacre any page it is inserted into.Sammysamoan
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.Oly
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.Sammysamoan
Hahaha, I just had to log in to applaud you guys ... these comments are pure gold, indeed.Juli
Yeah the * operator in CSS alone is expensive. Please don't add alert(...) too. And the 1ms poll omg!Mallory

© 2022 - 2024 — McMap. All rights reserved.