294 votes

Détecter les changements dans le DOM

Je veux exécuter une fonction lorsqu'un div ou un input est ajouté au html. Est-ce possible ?

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

1 votes

A moins qu'un script tiers ajoute les noeuds au DOM, ce n'est pas nécessaire.

0 votes

0 votes

242voto

vsync Points 11280

La meilleure approche jusqu'à présent, avec le plus petit code :

(IE9+, FF, Webkit)

Utilisation de MutationObserver et de revenir à la version obsolète Événements de mutation si nécessaire :
(Exemple ci-dessous si seulement pour les changements DOM concernant les noeuds ajoutés ou enlevés)

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

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

    if( MutationObserver ){
      // define a new observer
      var mutationObserver = new MutationObserver(callback)

      // have the observer observe foo for changes in children
      mutationObserver.observe( obj, { childList:true, subtree:true })
      return mutationObserver
    }

    // browser support fallback
    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 votes

Cela semble fonctionner assez bien pour les nouveaux noeuds DOM. Pouvons-nous l'adapter pour qu'il gère également les changements de nœuds DOM (au moins les valeurs/le texte du nœud DOM ?).

2 votes

Passez le mutations, observer à la fonction de rappel pour un meilleur contrôle.

3 votes

Cela m'a beaucoup aidé, mais comment puis-je le "délier" ? Disons que je veux surveiller un changement une seule fois, mais le faire à plusieurs reprises ? oberserveDOM = null ne fonctionnera évidemment pas...

239voto

galambalazs Points 24393

Mise à jour de 2015, nouveau MutationObserver est prise en charge par les navigateurs modernes :

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

Si vous devez soutenir les plus anciens, vous pouvez essayer de vous rabattre sur d'autres approches, comme celles mentionnées dans le présent document. 5 ( !) réponse d'un an ci-dessous. Il y a des dragons. Amusez-vous bien :)


Quelqu'un d'autre modifie le document ? Parce que si vous avez le contrôle total des modifications, il vous suffit de créer votre propre système de gestion des documents. domChanged API - avec une fonction ou un événement personnalisé - et de le déclencher/appeler partout où vous modifiez les choses.

El Le niveau 2 de DOM a Types d'événements de mutation mais les anciennes versions d'IE ne le supportent pas. Notez que les événements de mutation sont déprécié dans la spécification DOM3 Events et avoir un pénalité de performance .

Vous pouvez essayer d'émuler l'é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 surcharge. Imaginez que vous ayez besoin de stocker l'état actuel de l'ensemble du document, et d'examiner toutes les propriétés de chaque élément pour qu'elles soient identiques.

Peut-être que si vous ne vous intéressez qu'aux éléments et à leur ordre (comme vous l'avez mentionné dans votre question), une 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);

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

0 votes

Ne pouvons-nous pas ajouter un auditeur actif (je ne sais pas comment dire !) ou quelque chose qui vérifie l'intervalle et le domaine ?

4 votes

Je me demande : comment jQuery live() résout-il ce problème s'il ne peut pas détecter un changement de DOM ?

1 votes

JoshStodola Le gras m'ennuyait aussi. J'ai décidé de le corriger.

32voto

Anthony Awuley Points 529

L'exemple suivant a été adapté de Mozilla Hacks'. article de blog et utilise MutationObserver .

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

Navigateurs pris en charge : Chrome 18+, Firefox 14+, IE 11+, Safari 6+.

13voto

HB MAAM Points 947

Ou vous pouvez simplement Créez 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/

1 votes

Ce n'est pas la même chose... la méthode décrite ci-dessus est toujours valable api.jquery.com/trigger

13voto

Adam Pietrasiak Points 728

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

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

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

La différence entre .each est - il prend votre sélecteur, dans ce cas-ci .some-element et attendre de nouveaux éléments avec ce sélecteur dans le futur, si un tel élément sera ajouté, il sera initialisé aussi.

Dans notre cas, la fonction initialize change juste la couleur de l'élément en bleu. Donc si nous ajoutons un nouvel élément (peu importe si c'est avec ajax ou même avec l'inspecteur F12 ou autre) comme :

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

Le plugin l'initialisera instantanément. Le plugin s'assure également qu'un élément n'est initialisé qu'une seule fois. Ainsi, si vous ajoutez un élément, alors .detach() et l'ajouter à 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 sur IE9 et 10 avec les dépendances telles que détaillées sur le site web de la Commission européenne. page readme .

1 votes

S'il vous plaît, ajoutez à npm.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X