web-dev-qa-db-fra.com

Détecter les changements dans le DOM

Je veux exécuter une fonction quand un div ou une entrée sont ajoutés au html . Est-ce possible?

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

184
esafwan

Mise à jour 2015, la nouvelle MutationObserver est prise 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 recourir à d’autres approches telles que celles mentionnées dans cette réponse 5 (!) D’un an ci-dessous. Il y a des dragons. Prendre plaisir :) 


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

DOM niveau 2 a types d'événements de mutation, mais l'ancienne version de IE ne le prend pas en charge. Notez que les événements de mutation sont obsolètes dans la spécification DOM3 Events } et ont un pénalité de performance .

Vous pouvez essayer d'émuler un événement de mutation avec onpropertychange dans IE (et revenir à l'approche par la force brute si aucune d'entre elles n'est disponible).

Pour un complet domChange, un intervalle pourrait être une tuerie excessive. Imaginez que vous deviez stocker l'état actuel de l'ensemble du document et examiner chaque propriété de chaque élément comme étant la même.

Peut-être que si vous êtes uniquement intéressé par les éléments et leur ordre (comme vous l'avez mentionné dans votre question), un _getElementsByTagName("*") peut fonctionner. Cela se déclenchera automatiquement si vous ajoutez un élément, supprimez un élément, remplacez des éléments ou modifiez 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);

Usage:

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

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

171
gblazex

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

IE9 +, FF, Webkit: 

Utiliser MutationObserver et revenir à la version obsolète Événements de mutation si nécessaire:
(Exemple ci-dessous, s'il ne concerne que les modifications du DOM concernant les nœuds ajoutés ou supprimés)}

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>

195
vsync

J'ai récemment écrit un plugin qui fait exactement cela - jquery.initialize

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

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

La différence par rapport à .each est la suivante: il faut votre sélecteur, dans ce cas .some-element et attendre que de nouveaux éléments avec ce sélecteur soient utilisés à l'avenir, si cet élément est ajouté, il sera également initialisé.

Dans notre cas, la fonction d'initialisation change simplement la couleur de l'élément en bleu. Donc, si nous ajoutons un nouvel élément (peu importe que ce soit avec ajax ou même un inspecteur F12 ou quoi que ce soit) comme:

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

Le plugin l'initialisera instantanément. Aussi, le plugin s'assure qu'un élément n'est initialisé qu'une fois. Donc, si vous ajoutez un élément, puis .detach() le à partir du corps, puis ajoutez-le à nouveau, il ne sera pas initialisé à nouveau.

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

Le plugin est basé sur MutationObserver - il fonctionnera sous IE9 et 10 avec les dépendances détaillées dans la page readme .

13
pie6k

ou vous pouvez simplement Créer votre propre événement, qui se déroule 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
HB MAAM

Ceci est un exemple utilisant MutationObserver from Mozilla adapté de ceci blog post

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();
2
Anthony Awuley

Que diriez-vous d'étendre une requête pour cela? 

   (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 un support pour cela (j'ai entendu dire non testé).

0
StartCoding

Utilisez le MutationObserver interface comme indiqué dans Gabriele Romanato's 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
Anthony Awuley