Alternative to DOMNodeInserted

26,565

Solution 1

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

Solution 2

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

Share:
26,565
Shane Larson
Author by

Shane Larson

Living in southern Brazil.

Updated on February 03, 2022

Comments

  • Shane Larson
    Shane Larson over 2 years

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

    • Šime Vidas
      Šime Vidas over 12 years
      You need that feature for the entire DOM? And at all times?
    • Wladimir Palant
      Wladimir Palant over 12 years
      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.
    • Shane Larson
      Shane Larson over 12 years
      Šime Vidas, yes I do, some scripts insert <object> tags when the user interacts with the page.
    • Šime Vidas
      Šime Vidas over 12 years
      @Fabio Are those scripts third-party or your scripts?
    • Shane Larson
      Shane Larson over 12 years
      Third-party. I have an extension that keeps track of content in webpages.
    • Radu
      Radu over 12 years
      So are you interested in object or the entire DOM?
    • Shane Larson
      Shane Larson over 12 years
      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>).
    • csuwldcat
      csuwldcat over 11 years
      @Fábio checkout the new answer I added, I believe you'll like it ;)
  • Admin
    Admin over 12 years
    This is terrible code that will massacre any page it is inserted into.
  • Anonymous
    Anonymous over 12 years
    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.
  • Admin
    Admin over 12 years
    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.
  • Rufus
    Rufus over 11 years
    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.
  • naugtur
    naugtur over 10 years
    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/insertionQuery
  • csuwldcat
    csuwldcat about 10 years
    @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
    csuwldcat about 10 years
    @naugtur it's now updated on Github, and I've tweaked this answer to match - thanks for the heads-up!
  • naugtur
    naugtur about 10 years
    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
    naugtur about 10 years
    @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.
  • csuwldcat
    csuwldcat about 10 years
    @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 ;)
  • Leo
    Leo almost 10 years
    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.
  • csuwldcat
    csuwldcat almost 10 years
    @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.
  • Leo
    Leo almost 10 years
    @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...
  • csuwldcat
    csuwldcat almost 10 years
    For your use, I would checkout X-Tag (x-tags.org) or Polymer (polymer-project.org) - Web Components do exactly what you want.
  • csuwldcat
    csuwldcat over 9 years
    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/SelectorListener
  • Barney
    Barney over 9 years
    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).
  • csuwldcat
    csuwldcat over 9 years
    @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: github.com/csuwildcat/SelectorListener, and 2) Mutation Observers aren't supported in older browsers like IE9, Safari 5, 6, etc.
  • csuwldcat
    csuwldcat over 9 years
    @Barney I've updated my answer with another use-case that makes it clear what this method can do easily that Mutation Observers cannot.
  • Barney
    Barney over 9 years
    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.
  • csuwldcat
    csuwldcat over 9 years
    @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
    csuwldcat over 9 years
    @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 ;)
  • Barney
    Barney over 9 years
    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
    Barney over 9 years
    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.
  • aefxx
    aefxx over 7 years
    Hahaha, I just had to log in to applaud you guys ... these comments are pure gold, indeed.
  • Matt W
    Matt W over 3 years
    Yeah the * operator in CSS alone is expensive. Please don't add alert(...) too. And the 1ms poll omg!