Détecter les changements dans le DOM

je veux exécuter une fonction lorsqu'une div ou une entrée est ajoutée au html. Est-ce possible?

par exemple, une entrée de texte est ajoutée, alors la fonction doit être appelée.

176
demandé sur Joe Frambach 2010-07-10 19:21:07

7 réponses

mise à jour 2015, nouveau MutationObserver est pris en charge par les navigateurs modernes:

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

si vous avez besoin de soutenir les plus âgés, vous pouvez essayer de revenir à d'autres approches comme celles mentionnées dans ce 5 (!) ans réponse ci-dessous. Il y a des dragons. Enjoy:)


Quelqu'un d'autre est en train de changer le document? Parce que si vous avez le contrôle total sur les modifications, vous avez juste besoin de créer votre propre API domChanged - avec une fonction ou un événement personnalisé - et de le déclencher/l'appeler partout où vous modifiez des choses.

le DOM Niveau 2 a types D'événements de Mutation , mais la version plus ancienne de IE ne le supporte pas. Notez que les événements de mutation sont dépréciés dans les événements DOM3 spec et ont un performances .

vous pouvez essayer d'imiter l'événement de mutation avec onpropertychange dans IE (et revenir à l'approche de la force brute si aucun d'eux n'est disponible).

Pour un plein domChange un intervalle peut être une sur-tuer. Imaginez que vous avez besoin pour stocker l'état actuel de l'ensemble du document, et d'examiner chaque élément de chaque propriété d'être le même.

peut-être que si vous êtes seulement intéressé par les éléments et leur ordre (comme vous l'avez mentionné dans votre question), un getElementsByTagName("*") peut fonctionner. Cela déclenche automatiquement si vous ajoutez un élément, supprimer un élément, remplacer des éléments ou modifier la structure du document.

j'ai écrit une preuve de concept:

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

Utilisation:

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

cela fonctionne sur IE 5.5+, FF 2+, Chrome, Safari 3+ et Opera 9.6+

168
répondu galambalazs 2015-07-24 12:28:16

c'est l'approche ultime jusqu'à présent, avec le plus petit code:

IE9+, FF, Webkit:

en utilisant MutationObserver et en retombant à la valeur dépréciée événements de Mutation si nécessaire:

(exemple ci-dessous si seulement pour les modifications DOM concernant les noeuds ajouté ou supprimé)

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>
189
répondu vsync 2018-09-26 07:29:59

j'ai récemment écrit un plugin qui fait exactement cela - jquery.initialiser

Vous l'utilisez de la même manière que .each fonction

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

la différence de .each est - il prend votre sélecteur, dans ce cas .some-element et attendre de nouveaux éléments avec ce sélecteur à l'avenir, si un tel élément sera ajouté, il sera initialisé aussi.

Dans notre cas initialiser fonction il suffit de changer la couleur de l'élément en bleu. Donc, si nous allons ajouter un nouvel élément (peu importe si avec ajax ou même F12 inspecteur ou quoi que ce soit) comme:

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

Plugin l'initera instantanément. De plus, le plugin s'assure qu'un élément n'est initialisé qu'une seule fois. Donc, si vous ajoutez l'élément, puis .detach() du corps et de l'ajouter de nouveau, il ne sera pas initialisé à nouveau.

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

Plugin est basé sur MutationObserver - il fonctionnera sur IE9 et 10 avec les dépendances comme détaillé sur le readme page .

13
répondu pie6k 2017-11-14 15:38:48

ou vous pouvez simplement créer votre propre événement , qui courent partout

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

        });

exemple complet http://jsfiddle.net/hbmaam/Mq7NX /

9
répondu HB MAAM 2014-09-28 08:53:38

Ceci est un exemple utilisant MutationObserver de Mozilla adapté de post de 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();
1
répondu Anthony Awuley 2018-05-23 17:35:40

pourquoi ne pas étendre un jquery pour ça?

   (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+ a construit le support pour cela (j'ai entendu non testé).

0
répondu StartCoding 2017-02-13 09:24:21

Utiliser JQuery MutationObserver comme le montre Gabriele Romanato blog

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();
0
répondu Anthony Awuley 2018-05-23 17:35:25