Rileva le modifiche nel DOM


242

Voglio eseguire una funzione quando alcuni div o input vengono aggiunti all'html. È possibile?

Ad esempio, viene inserito un input di testo, quindi è necessario chiamare la funzione.


1
A meno che alcuni script di terze parti non aggiungano i nodi al DOM, questo non è necessario.
Justin Johnson,



4
@JustinJohnson se stai realizzando un'estensione di Chrome che inietta codice JS, è utile.
FluorescentGreen5,

Risposte:


207

Aggiornamento 2015, il nuovo MutationObserverè supportato dai browser moderni:

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

Se hai bisogno di supportare quelli più anziani, potresti provare a ricorrere ad altri approcci come quelli menzionati in questa risposta di 5 (!) Anni di seguito. Ci sono draghi. Godere :)


Qualcun altro sta cambiando il documento? Perché se hai il pieno controllo delle modifiche devi solo creare la tua domChangedAPI - con una funzione o un evento personalizzato - e attivarla / chiamarla ovunque modifichi le cose.

Il DOM Level-2 ha tipi di eventi di mutazione , ma vecchia versione di IE non lo supportano. Si noti che gli eventi di mutazione sono deprecati nelle specifiche Eventi DOM3 e hanno una penalità di prestazione .

Puoi provare a emulare l'evento di mutazione con onpropertychangein IE (e ricorrere all'approccio a forza bruta se non è disponibile uno di essi).

Per un domChange completo un intervallo potrebbe essere un over-kill. Immagina di dover archiviare lo stato corrente dell'intero documento ed esaminare ogni proprietà di ogni elemento per essere la stessa.

Forse se sei interessato solo agli elementi e al loro ordine (come hai menzionato nella tua domanda), a getElementsByTagName("*") può funzionare. Questo si attiverà automaticamente se aggiungi un elemento, rimuovi un elemento, sostituisci elementi o modifichi la struttura del documento.

Ho scritto una prova di concetto:

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

Uso:

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

Funziona su IE 5.5+, FF 2+, Chrome, Safari 3+ e Opera 9.6+


4
Chiedendosi: come fa jQuery live () a risolvere questo problema se non riescono a rilevare una modifica DOM?
Kees C. Bakker,

163
Non posso credere che nel 2010 hai avuto IE5.5 per testare questo.
Oscar Godson,

1
@JoshStodola Anche l'audace mi dava fastidio. Ho deciso di aggiustarlo.
Bojangles,

1
Gli eventi di mutazioni sono deprecati. È necessario utilizzare MutationObserver. Ho scritto il mio plugin per problemi come questo - github.com/AdamPietrasiak/jquery.initialize
pie6k

1
Come posso fare in modo che jquery onClick si attivi prima di un osservatore di mutazione, che si attiva quando si fa clic su un pulsante con un'azione di brace? stackoverflow.com/questions/29216434/...
SuperUberDuper

219

Questo è l'approccio finale finora, con il codice più piccolo:

IE9 +, FF, Webkit:

Uso di MutationObserver e , se necessario, ritorno agli eventi di Mutazione obsoleti :
(Esempio di seguito se solo per modifiche DOM relative ai nodi aggiunti o rimossi)

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
sembra funzionare abbastanza bene per i nuovi nodi DOM. Possiamo adattarlo per gestire anche le modifiche al nodo dom (almeno i valori / testo del nodo DOM?)
Sebastien Lorber,

8
@SebastienLorber - chi è "noi"? tu, come programmatore, puoi prendere questo codice e usarlo come desideri. basta leggere su MDN per quali cose è possibile osservare il DOM e per quali no.
vsync,

2
Passare i mutations, observerparametri alla funzione di richiamata per un maggiore controllo.
A1rPun

2
Questo mi ha aiutato molto, ma come posso "sciogliere" questo? Supponiamo che voglia vedere un cambiamento solo una volta, ma farlo in più occasioni? oberserveDOM = null ovviamente non funzionerà ...
stiller_leser,

Perché questo dovrebbe funzionare solo per aggiunto / rimosso? Sembra che gli eventi di mutazione coprano più di così ... developer.mozilla.org/en-US/docs/Web/Guide/Events/…
f0ster

15

Di recente ho scritto un plugin che fa esattamente questo: jquery.initialize

Lo usi allo stesso modo della .eachfunzione

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

La differenza .eachè: prende il tuo selettore, in questo caso .some-elemente aspetta nuovi elementi con questo selettore in futuro, se tale elemento verrà aggiunto, verrà anche inizializzato.

Nel nostro caso la funzione di inizializzazione cambia semplicemente il colore dell'elemento in blu. Quindi, se aggiungeremo un nuovo elemento (non importa se con ajax o anche con l'ispettore F12 o altro) come:

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

Il plug-in lo avvia immediatamente. Inoltre il plugin si assicura che un elemento sia inizializzato solo una volta. Quindi, se aggiungi un elemento, quindi .detach()dal corpo e poi lo aggiungi di nuovo, non verrà inizializzato di nuovo.

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

Il plugin si basa su MutationObserver- funzionerà su IE9 e 10 con dipendenze come dettagliato nella pagina del readme .


Per favore, aggiungi a npm.
thexpand,

14

oppure puoi semplicemente creare il tuo evento , che si svolge ovunque

 $("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");

        });

Esempio completo http://jsfiddle.net/hbmaam/Mq7NX/


questo non è lo stesso ... il metodo sopra descritto è ancora valido api.jquery.com/trigger
lefoy

11

Questo è un esempio usando MutationObserver di Mozilla adattato da questo post del blog

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

Utilizzare l' interfaccia MutationObserver come mostrato nel blog di Gabriele Romanato

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 è JavaScript nativo, non jQuery.
Benjamin,

2

Che ne dici di estendere un jquery per questo?

   (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+ ha creato supporto per questo (ho sentito non testato).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.