Erkennen Sie Änderungen im DOM


242

Ich möchte eine Funktion ausführen, wenn dem HTML-Code div oder input hinzugefügt werden. Ist das möglich?

Zum Beispiel wird eine Texteingabe hinzugefügt, dann sollte die Funktion aufgerufen werden.


1
Dies ist nicht erforderlich, es sei denn, ein Skript eines Drittanbieters fügt die Knoten zum DOM hinzu.
Justin Johnson



4
@JustinJohnson Wenn Sie eine Chrome-Erweiterung erstellen, die JS-Code einfügt, ist dies nützlich.
FluorescentGreen5

Antworten:


206

Update 2015, neu MutationObserverwird von modernen Browsern unterstützt:

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

Wenn Sie ältere unterstützen müssen, können Sie versuchen, auf andere Ansätze zurückzugreifen, wie die in dieser 5 (!) Jahre alten Antwort unten genannten. Es gibt Drachen. Genießen :)


Jemand anderes ändert das Dokument? Denn wenn Sie die volle Kontrolle über die Änderungen haben, müssen Sie nur Ihre eigene domChangedAPI erstellen - mit einer Funktion oder einem benutzerdefinierten Ereignis - und sie überall dort auslösen / aufrufen, wo Sie Dinge ändern.

Das DOM Level-2 verfügt über Mutation-Ereignistypen , ältere Versionen des IE unterstützen dies jedoch nicht. Beachten Sie, dass die Mutationsereignisse in der DOM3-Ereignisspezifikation veraltet sind und eine Leistungsbeeinträchtigung aufweisen .

Sie können versuchen, ein Mutationsereignis mit onpropertychangeim IE zu emulieren (und auf den Brute-Force-Ansatz zurückgreifen, wenn keiner von ihnen verfügbar ist).

Für einen vollständigen domChange kann ein Intervall ein Over-Kill sein. Stellen Sie sich vor, Sie müssen den aktuellen Status des gesamten Dokuments speichern und prüfen, ob jede Eigenschaft jedes Elements gleich ist.

Wenn Sie nur an den Elementen und ihrer Reihenfolge interessiert sind (wie Sie in Ihrer Frage erwähnt haben), getElementsByTagName("*")kann a möglicherweise funktionieren. Dies wird automatisch ausgelöst, wenn Sie ein Element hinzufügen, ein Element entfernen, Elemente ersetzen oder die Struktur des Dokuments ändern.

Ich habe einen Proof of Concept geschrieben:

(function (window) {
    var last = +new Date();
    var delay = 100; // default delay

    // Manage event queue
    var stack = [];

    function callback() {
        var now = +new Date();
        if (now - last > delay) {
            for (var i = 0; i < stack.length; i++) {
                stack[i]();
            }
            last = now;
        }
    }

    // Public interface
    var onDomChange = function (fn, newdelay) {
        if (newdelay) delay = newdelay;
        stack.push(fn);
    };

    // Naive approach for compatibility
    function naive() {

        var last = document.getElementsByTagName('*');
        var lastlen = last.length;
        var timer = setTimeout(function check() {

            // get current state of the document
            var current = document.getElementsByTagName('*');
            var len = current.length;

            // if the length is different
            // it's fairly obvious
            if (len != lastlen) {
                // just make sure the loop finishes early
                last = [];
            }

            // go check every element in order
            for (var i = 0; i < len; i++) {
                if (current[i] !== last[i]) {
                    callback();
                    last = current;
                    lastlen = len;
                    break;
                }
            }

            // over, and over, and over again
            setTimeout(check, delay);

        }, delay);
    }

    //
    //  Check for mutation events support
    //

    var support = {};

    var el = document.documentElement;
    var remain = 3;

    // callback for the tests
    function decide() {
        if (support.DOMNodeInserted) {
            window.addEventListener("DOMContentLoaded", function () {
                if (support.DOMSubtreeModified) { // for FF 3+, Chrome
                    el.addEventListener('DOMSubtreeModified', callback, false);
                } else { // for FF 2, Safari, Opera 9.6+
                    el.addEventListener('DOMNodeInserted', callback, false);
                    el.addEventListener('DOMNodeRemoved', callback, false);
                }
            }, false);
        } else if (document.onpropertychange) { // for IE 5.5+
            document.onpropertychange = callback;
        } else { // fallback
            naive();
        }
    }

    // checks a particular event
    function test(event) {
        el.addEventListener(event, function fn() {
            support[event] = true;
            el.removeEventListener(event, fn, false);
            if (--remain === 0) decide();
        }, false);
    }

    // attach test events
    if (window.addEventListener) {
        test('DOMSubtreeModified');
        test('DOMNodeInserted');
        test('DOMNodeRemoved');
    } else {
        decide();
    }

    // do the dummy test
    var dummy = document.createElement("div");
    el.appendChild(dummy);
    el.removeChild(dummy);

    // expose
    window.onDomChange = onDomChange;
})(window);

Verwendung:

onDomChange(function(){ 
    alert("The Times They Are a-Changin'");
});

Dies funktioniert unter IE 5.5+, FF 2+, Chrome, Safari 3+ und Opera 9.6+


4
Fragen Sie sich: Wie löst jQuery live () dieses Problem, wenn sie keine DOM-Änderung erkennen können?
Kees C. Bakker

162
Ich kann nicht glauben, dass Sie 2010 IE5.5 hatten, um dies zu testen.
Oscar Godson

1
@ JoshStodola Der Mut hat mich auch geärgert. Ich habe beschlossen, es zu beheben.
Bojangles

1
Mutationsereignisse sind veraltet. Sie sollten MutationObserver verwenden. Ich habe mein Plugin für Probleme wie dieses geschrieben - github.com/AdamPietrasiak/jquery.initialize
pie6k

1
Wie kann ich jquery onClick dazu bringen, vor einem Mutationsbeobachter zu feuern, der ausgelöst wird, wenn auf eine Schaltfläche mit einer Glutaktion geklickt wird? stackoverflow.com/questions/29216434/…
SuperUberDuper

219

Dies ist der bisher ultimative Ansatz mit kleinstem Code:

IE9 +, FF, Webkit:

Verwenden von MutationObserver und Zurückgreifen auf die veralteten Mutationsereignisse, falls erforderlich:
(Beispiel unten, wenn nur für DOM-Änderungen in Bezug auf angehängte oder entfernte Knoten)

var observeDOM = (function(){
  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

  return function( obj, callback ){
    if( !obj || !obj.nodeType === 1 ) return; // validation

    if( MutationObserver ){
      // define a new observer
      var obs = new MutationObserver(function(mutations, observer){
          callback(mutations);
      })
      // have the observer observe foo for changes in children
      obs.observe( obj, { childList:true, subtree:true });
    }
    
    else if( window.addEventListener ){
      obj.addEventListener('DOMNodeInserted', callback, false);
      obj.addEventListener('DOMNodeRemoved', callback, false);
    }
  }
})();

//------------< DEMO BELOW >----------------
// add item
var itemHTML = "<li><button>list item (click to delete)</button></li>",
    listElm = document.querySelector('ol');

document.querySelector('body > button').onclick = function(e){
  listElm.insertAdjacentHTML("beforeend", itemHTML);
}

// delete item
listElm.onclick = function(e){
  if( e.target.nodeName == "BUTTON" )
    e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
    
// Observe a specific DOM element:
observeDOM( listElm, function(m){ 
   var addedNodes = [], removedNodes = [];

   m.forEach(record => record.addedNodes.length & addedNodes.push(...record.addedNodes))
   
   m.forEach(record => record.removedNodes.length & removedNodes.push(...record.removedNodes))

  console.clear();
  console.log('Added:', addedNodes, 'Removed:', removedNodes);
});


// Insert 3 DOM nodes at once after 3 seconds
setTimeout(function(){
   listElm.removeChild(listElm.lastElementChild);
   listElm.insertAdjacentHTML("beforeend", Array(4).join(itemHTML));
}, 3000);
<button>Add Item</button>
<ol>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><em>&hellip;More will be added after 3 seconds&hellip;</em></li>
</ol>


1
Es scheint ziemlich gut für neue DOM-Knoten zu funktionieren. Können wir es anpassen, um auch Änderungen an Domknoten zu verarbeiten (zumindest die DOM-Knotenwerte / Text?)
Sebastien Lorber

8
@SebastienLorber - wer ist "wir"? Als Programmierer können Sie diesen Code verwenden, wie Sie möchten. Lesen Sie einfach auf dem MDN, für welche Dinge Sie das DOM beobachten können und für welche nicht.
vsync

2
Übergeben Sie die mutations, observerParameter zur besseren Kontrolle an die Rückruffunktion.
A1rPun

2
Das hat mir sehr geholfen, aber wie kann ich das "lösen"? Angenommen, ich möchte nur einmal auf eine Änderung achten, dies jedoch mehrmals? oberserveDOM = null wird offensichtlich nicht funktionieren ...
stiller_leser

Warum funktioniert dies nur für angehängte / entfernte? Es sieht so aus, als ob die Mutationsereignisse mehr als das abdecken. Developer.mozilla.org/en-US/docs/Web/Guide/Events/…
f0ster

15

Ich habe kürzlich ein Plugin geschrieben, das genau das tut - jquery.initialize

Sie verwenden es genauso wie die .eachFunktion

$(".some-element").initialize( function(){
    $(this).css("color", "blue"); 
});

Der Unterschied zu .eachist - in diesem Fall ist Ihr Selektor erforderlich.some-element und in Zukunft mit diesem Selektor auf neue Elemente gewartet wird. Wenn ein solches Element hinzugefügt wird, wird es auch initialisiert.

In unserem Fall ändern Sie die Initialisierungsfunktion einfach die Elementfarbe in Blau. Wenn wir also ein neues Element hinzufügen (egal ob mit Ajax oder sogar F12 Inspector oder so), wie:

$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!

Das Plugin wird es sofort starten. Außerdem stellt das Plugin sicher, dass ein Element nur einmal initialisiert wird. Wenn Sie also ein Element hinzufügen, dann.detach() aus dem Textkörper und dann erneut hinzufügen, wird es nicht erneut initialisiert.

$("<div/>").addClass('some-element').appendTo("body").detach()
    .appendTo(".some-container");
//initialized only once

Das Plugin basiert auf MutationObserver- es funktioniert unter IE9 und 10 mit Abhängigkeiten, wie auf der Readme-Seite beschrieben .


Bitte zu npm hinzufügen.
Thexpand

14

oder Sie können einfach Ihr eigenes Ereignis erstellen , das überall ausgeführt wird

 $("body").on("domChanged", function () {
                //dom is changed 
            });


 $(".button").click(function () {

          //do some change
          $("button").append("<span>i am the new change</span>");

          //fire event
          $("body").trigger("domChanged");

        });

Vollständiges Beispiel http://jsfiddle.net/hbmaam/Mq7NX/


das ist nicht dasselbe ... die oben beschriebene Methode ist immer noch gültig api.jquery.com/trigger
lefoy

10

Dies ist ein Beispiel für die Verwendung von MutationObserver von Mozilla, angepasst an diesen Blog-Beitrag

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');

// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };

// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

// Later, you can stop observing
observer.disconnect();

4

Verwenden Sie die MutationObserver- Oberfläche, wie im Blog von Gabriele Romanato gezeigt

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

// The node to be monitored
var target = $( "#content" )[0];

// Create an observer instance
var observer = new MutationObserver(function( mutations ) {
  mutations.forEach(function( mutation ) {
    var newNodes = mutation.addedNodes; // DOM NodeList
    if( newNodes !== null ) { // If there are new nodes added
        var $nodes = $( newNodes ); // jQuery set
        $nodes.each(function() {
            var $node = $( this );
            if( $node.hasClass( "message" ) ) {
                // do something
            }
        });
    }
  });    
});

// Configuration of the observer:
var config = { 
    attributes: true, 
    childList: true, 
    characterData: true 
};

// Pass in the target node, as well as the observer options
observer.observe(target, config);

// Later, you can stop observing
observer.disconnect();

3
MutationObserver ist natives JavaScript, nicht jQuery.
Benjamin

2

Wie wäre es damit, eine Abfrage dafür zu erweitern?

   (function () {
        var ev = new $.Event('remove'),
            orig = $.fn.remove;
        var evap = new $.Event('append'),
           origap = $.fn.append;
        $.fn.remove = function () {
            $(this).trigger(ev);
            return orig.apply(this, arguments);
        }
        $.fn.append = function () {
            $(this).trigger(evap);
            return origap.apply(this, arguments);
        }
    })();
    $(document).on('append', function (e) { /*write your logic here*/ });
    $(document).on('remove', function (e) { /*write your logic here*/ ) });

Jquery 1.9+ hat Unterstützung dafür aufgebaut (ich habe gehört, nicht getestet).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.