Обнаружение изменений в DOM


Я хочу выполнить функцию, когда некоторые div или входные данные добавляются в html. Это возможно?

например, добавляется текстовый ввод, затем должна быть вызвана функция.

7 176

7 ответов:

обновление 2015, новый MutationObserver поддерживается современными браузерами:

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

Если вам нужно поддерживать старые, вы можете попытаться вернуться к другим подходам, как те, которые упомянуты в этом 5 (!) год назад ответ ниже. Там есть драконы. Наслаждайтесь :)


кто-то еще менять документ? Потому что если у вас есть полный контроль над изменениями вы просто нужно создать свой собственный domChanged API - с функцией или пользовательским событием-и запускать / вызывать его везде, где вы изменяете вещи.

The DOM Level-2 имеет типы событий мутации, но более старая версия IE не поддерживает его. Обратите внимание, что события мутации устарело в спецификации событий DOM3 и производительность.

вы можете попробовать эмулировать событие мутации с onpropertychange в IE (и вернитесь к подходу грубой силы, если ни один из них не доступен).

на полное domChange интервал может быть чрезмерным убийством. Представьте, что вам нужно сохранить текущее состояние всего документа и изучить каждое свойство каждого элемента, чтобы быть одинаковым.

может быть, если вас интересуют только элементы и их порядок (как вы упомянули в своем вопросе), a getElementsByTagName("*") может работать. Это сработает автоматически, если вы добавите элемент, удалите элемент, заменить элементы или изменить структуру документа.

я написал доказательство концепции:

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

использование:

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

это работает на IE 5.5+, FF 2+, Chrome, Safari 3+ и Opera 9.6+

Это окончательный подход до сих пор, с наименьшим кодом:

IE9+, FF, Webkit:

используя MutationObserver и возвращаясь к устаревшим событий мутации если нужно:
(пример ниже, если только для изменений DOM, касающихся добавленных или удаленных узлов)

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>

Я недавно написал плагин, который делает именно это - jquery.инициализировать

вы используете его так же, как .each функции

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

в отличие от .each это-он принимает ваш селектор, в этом случае .some-element и ждать новых элементов с этим селектором в будущем, если такой элемент будет добавлен, он будет слишком инициализирован.

в нашем случае инициализировать функцию просто изменить цвет элемента на синий. Так что если мы добавим новые элемент (независимо от того, если с ajax или даже F12 инспектор или что-нибудь), как:

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

плагин будет инициализировать его мгновенно. Также плагин гарантирует, что один элемент инициализируется только один раз. Так что если вы добавляете элемент, то .detach() это от тела, а затем добавить его снова, он не будет инициализирован.

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

плагин основан на MutationObserver - он будет работать на IE9 и 10 с зависимостями, как описано на readme page.

или вы можете просто Создайте свое собственное событие, которые работают везде

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

        });

полный пример http://jsfiddle.net/hbmaam/Mq7NX/

Это пример использования MutationObserver С Mozilla адаптировано из этой блоге

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

Как насчет расширения jQuery для этого?

   (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+ имеет встроенную поддержку для этого(я слышал, не проверял).

Использовать JQuery MutationObserver как показано в блог

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