Обнаружить изменения в DOM

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

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

11 ответов

Решение

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

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

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


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

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

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

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

Может быть, если вас интересуют только элементы и их порядок (как вы упомянули в своем вопросе), 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 и возврат к устаревшим событиям Mutation, если необходимо:
(Пример ниже, если только для изменений 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>

Это пример использования 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.initialize

Вы используете его так же, как .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.

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function(mutations, observer) {
    // fired when a mutation occurs
    console.log(mutations, observer);
    // ...
});

// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
  subtree: true,
  attributes: true
  //...
});

Полные объяснения: /questions/12906600/est-slushatel-izmeneniya-dom-javascriptjquery/12906611#12906611

Или вы можете просто создать свое собственное событие, которое будет проходить везде

 $("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/

Используйте 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();

8 лет спустя вот мое решение

Следующая функция выполняет указанный файл обратного вызова при изменении DOM.

      watchDOMChange($('#dom-changes-here'),
  function() { console.log(`DOM Change happened ${new Date().getTime()}\n`);},
  {expires: 5000}
);

Функции

  • Отменяйте последовательные изменения DOM, чтобы предотвратить слишком много выполнений
  • Прекратить смотреть по истечении заданного времени
  • Полезно наблюдать за изменением DOM из фреймворка, например Angular
      function debounce(func, wait = 500 ) {
  var timeout;
  return function (...args) {
    var context = this;
    var later = function () {
      timeout = null;
      func.apply(context, args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

/**
 * Utility function watchDOMChange
 */
function watchDOMChange(el, callback, options={}) {
  options = Object.assign({debounce:100, expires:1000}, options);
  callback = callback || function() {};

  // watch DOM change and fire 'dom-change' event
  const observer = new MutationObserver(list =>  {
    el.dispatchEvent(new CustomEvent('dom-change', {detail: list}));
  });
  observer.observe(el, {attributes: false, childList: true, subtree: true });

  // listen to 'dom-change' event execute the callback function debounced
  const debouncedFunc = debounce(callback, options.debounce);
  el.addEventListener('dom-change', debouncedFunc);

  // fire min. once
  el.dispatchEvent(new CustomEvent('dom-change', {detail: undefined})); 

  // stop watching DOM change after expiry time
  if (options.expires) {
    setTimeout(function() {
      observer.disconnect(el);
      el.removeEventListener('dom-change', debouncedFunc);
    }, options.expires);
  }
}

Демо здесь, на stackblitz.

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

Нашел вопрос, поэтому обновил решение в 2022 году.

Мы видели различные решения, которые в основном включаютMutationObserver.

Если кто-то хочет записать изменения DOM и сохранить их для воспроизведения через некоторое время, он может использовать

Редактировать:

Добавляя пример, вот подсказки:

можно использовать через или npm

Возьмем пример CDNCDN для записи событий изменения DOM:

Шаг 1: просто включите следующий тег script в<HTML><head>ярлык

<script src="https://cdn.jsdelivr.net/npm/rrweb@2.0.0-alpha.2/dist/rrweb-all.js" crossorigin="anonymous"></script>

Шаг 2: и добавьте приведенный ниже код в свой код для захвата событий, сгенерированных rrweb.rrwebrrweb .

      <script>
var events = [];
rrweb.record({
    emit(event) {
       events.push(event);
       // you can store this event anywhere and you can replay them later. ex: some JSON file, or DB
    }
});

</script>

Этот пример в основном предназначен для записи событий для любого веб-приложения.

Чтобы знать и понимать в деталях (как записывать/воспроизводить), пожалуйста, прочитайте документацию rrweb .

Пример реплеера:

Это было сделано для отладки, однако добавлено сюда, чтобы каждый мог также проверить воспроизведенную сторону:

Пример реплеера

Используйте TrackChanges для обнаружения изменений HTML. Ссылка: https://www.npmjs.com/package/track-changes-js

Пример

       let button = document.querySelector('.button');

 trackChanges.addObserver('buttonObserver', () => button);
 
 trackChanges.addHandler('buttonObserver', buttonHandler);

 function buttonHandler(button) {
   console.log(`Button created: ${button}`);
 }
Другие вопросы по тегам