Виявити зміни в DOM


242

Я хочу виконати функцію, коли якийсь div або input буде додано до html. Чи можливо це?

Наприклад, додається введення тексту, тоді слід викликати функцію.


1
Якщо якийсь сторонній скрипт не додає вузли до DOM, це не потрібно.
Джастін Джонсон



4
@JustinJohnson, якщо ви робите хромоване розширення, яке вводить код JS, це корисно.
FluorescentGreen5

Відповіді:


206

Оновлення 2015 року, нове MutationObserverпідтримується сучасними браузерами:

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

Якщо вам потрібно підтримати старші, ви можете спробувати повернутися до інших підходів, таких як зазначені у цій відповіді 5 (!) Років 5 нижче. Будуть дракони. Насолоджуйтесь :)


Хтось ще змінює документ? Тому що, якщо ви маєте повний контроль над змінами, вам просто потрібно створити власний domChangedAPI - з функцією або користувацькою подією - та запустити / викликати його всюди, де ви змінюєте речі.

DOM Level-2 має типи подій мутації , але стара версія 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+


4
Цікаво: як jQuery live () вирішує цю проблему, якщо вони не можуть виявити зміну DOM?
Kees C. Bakker

163
Я не можу повірити, що у 2010 році у вас був IE5.5 для перевірки цього.
Оскар Годсон

1
@JoshStodola Сміливий дратував і мене. Я вирішив це виправити.
Bojangles

1
Події мутацій застаріли. Ви повинні використовувати MutationObserver. Я написав свій плагін для подібних проблем - github.com/AdamPietrasiak/jquery.initialize
pie6k

1
Як я можу заставити jquery onClick запускати перед спостерігачем мутації, який спрацьовує при натисканні кнопки із дією вугілля? stackoverflow.com/questions/29216434 / ...
SuperUberDuper

219

Це остаточний підхід поки що з найменшим кодом:

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>


1
здається, це працює досить добре для нових DOM-вузлів. Чи можемо ми пристосувати це також для обробки змін вузла дому (принаймні значення вузла / текст DOM?)
Себастьян Лорбер,

8
@SebastienLorber - хто "ми"? ви, як програміст, можете взяти цей код і використовувати його, як завгодно. просто прочитайте на MDN, за якими речами ви можете спостерігати за DOM, а які ви не можете.
vsync

2
Передайте mutations, observerпараметри функції зворотного виклику для додаткового контролю.
A1rPun

2
Це мені дуже допомогло, але як це "відв'язати"? Скажіть, я хочу спостерігати за зміною лише один раз, але робити це неодноразово? oberserveDOM = null очевидно не спрацює ...
stiller_leser

Чому це працює лише для доданих / вилучених? Схоже, події мутації охоплюють більше цього. Developer.mozilla.org/en-US/docs/Web/Guide/Events/…
f0ster

15

Нещодавно я написав плагін, який робить саме це - 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 .


Будь ласка, додайте до npm.
thexpand

14

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

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


це не те саме ... описаний вище метод все ще діє api.jquery.com/trigger
lefoy

11

Це приклад використання 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();

4

Використовуйте MutationObserver інтерфейс , як показано на Габріеле 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();

3
MutationObserver - це рідний JavaScript, а не jQuery.
Бенджамін

2

Як щодо розширення 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+ має підтримку для цього (я чув, що не перевірявся).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.