Detectar cambios en el DOM


Quiero ejecutar una función cuando se agrega algún div o entrada al html. Es esto posible?

Por ejemplo, se agrega una entrada de texto, luego se debe llamar a la función.

Author: Joe Frambach, 2010-07-10

7 answers

Actualización de 2015, nuevo MutationObserver es compatible con los navegadores modernos:

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

Si necesita apoyar a los más antiguos, puede intentar recurrir a otros enfoques como los mencionados en este 5 (!) year old answer below. Hay dragones. Disfruta :)


Alguien más está cambiando el documento? Porque si tienes control total sobre los cambios solo necesitas crear tu propia API domChanged - con un función o evento personalizado-y activarlo / llamarlo en cualquier lugar que modifique las cosas.

El DOM Nivel - 2 tiene Tipos de eventos de mutación, pero la versión anterior de IE no lo soporta. Tenga en cuenta que los eventos de mutación son obsoletos en la especificación de eventos DOM3 y tienen una penalización de rendimiento .

Puede intentar emular el evento de mutación con onpropertychange in IE (and fall back to the brute-force approach if non of them is available).

Para una completo domChange un intervalo podría ser un over-kill. Imagine que necesita almacenar el estado actual de todo el documento y examinar todas las propiedades de cada elemento para que sean iguales.

Tal vez si solo estás interesado en los elementos y su orden (como mencionaste en tu pregunta), un getElementsByTagName("*") puede funcionar. Esto se activará automáticamente si agrega un elemento, elimina un elemento, reemplaza elementos o cambia la estructura del documento.

Escribí una prueba de concepto:

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

Esto funciona en IE 5.5+, FF 2+, Chrome, Safari 3+ y Opera 9.6+

 168
Author: galambalazs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-24 12:28:16

Este es el enfoque último hasta ahora, con el código más pequeño:

IE9+, FF, Webkit:

Usando MutationObserver y volviendo al obsoleto Eventos de mutación si es necesario:
(Ejemplo a continuación si solo para cambios DOM relativos a nodos anexados o eliminados)

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
Author: vsync,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-26 07:29:59

Recientemente he escrito un plugin que hace exactamente eso - jquery.inicializar

Se usa de la misma manera que .each la función

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

La diferencia con .each es que toma su selector, en este caso .some-element y espera nuevos elementos con este selector en el futuro, si se agrega dicho elemento, también se inicializará.

En nuestro caso, la función inicializar solo cambia el color del elemento a azul. Así que si vamos a añadir nuevo elemento (no importa si con ajax o incluso F12 inspector o cualquier cosa) como:

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

El plugin lo iniciará instantáneamente. También plugin se asegura de que un elemento se inicializa solo una vez. Así que si agregas elemento, entonces .detach() desde body y luego lo agregas de nuevo, no se inicializará de nuevo.

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

Plugin se basa en MutationObserver - funcionará en IE9 y 10 con dependencias como se detalla en la página readme .

 13
Author: pie6k,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-14 15:38:48

O simplemente puede Crear su propio evento , que se ejecuta en todas partes

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

        });

Ejemplo Completo http://jsfiddle.net/hbmaam/Mq7NX /

 9
Author: HB MAAM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-28 08:53:38

Este es un ejemplo usando MutationObserver de Mozilla adaptado de este 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
Author: Anthony Awuley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-23 17:35:40

¿Qué tal extender un jquery para esto?

   (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 construido soporte para esto(he oído no probado).

 0
Author: StartCoding,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-13 09:24:21

Use jQuery MutationObserver como se muestra en el blog de 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();
 0
Author: Anthony Awuley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-23 17:35:25