Deteksi perubahan pada DOM


242

Saya ingin menjalankan fungsi ketika beberapa div atau input ditambahkan ke html. Apakah ini mungkin?

Misalnya, input teks ditambahkan, maka fungsinya harus dipanggil.


1
Kecuali beberapa skrip pihak ketiga menambahkan node ke DOM, ini tidak perlu.
Justin Johnson



4
@JustinJohnson jika Anda membuat ekstensi chrome yang menyuntikkan kode JS, ini berguna.
FluorescentGreen5

Jawaban:


207

Pembaruan 2015, baru MutationObserverdidukung oleh peramban modern:

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

Jika Anda perlu mendukung yang lebih lama, Anda dapat mencoba kembali ke pendekatan lain seperti yang disebutkan dalam jawaban 5 (!) Tahun di bawah ini. Ada naga. Nikmati :)


Orang lain mengganti dokumen? Karena jika Anda memiliki kontrol penuh atas perubahan, Anda hanya perlu membuat domChangedAPI Anda sendiri - dengan fungsi atau acara khusus - dan memicu / menyebutnya di mana pun Anda memodifikasi sesuatu.

The DOM Level-2 memiliki Mutasi jenis acara , namun versi yang lebih tua dari IE tidak mendukung hal itu. Perhatikan bahwa peristiwa mutasi tidak lagi digunakan dalam spesifikasi Acara DOM3 dan memiliki penalti kinerja .

Anda dapat mencoba meniru acara mutasi dengan onpropertychangedi IE (dan kembali ke pendekatan brute-force jika tidak ada yang tersedia).

Untuk domChange penuh, interval bisa menjadi over-kill. Bayangkan Anda perlu menyimpan keadaan saat ini dari seluruh dokumen, dan memeriksa setiap properti setiap elemen menjadi sama.

Mungkin jika Anda hanya tertarik pada elemen dan urutannya (seperti yang Anda sebutkan dalam pertanyaan Anda), sebuah getElementsByTagName("*")kaleng bisa berfungsi. Ini akan diaktifkan secara otomatis jika Anda menambahkan elemen, menghapus elemen, mengganti elemen atau mengubah struktur dokumen.

Saya menulis bukti konsep:

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

Pemakaian:

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

Ini berfungsi pada IE 5.5+, FF 2+, Chrome, Safari 3+ dan Opera 9.6+


4
Bertanya-tanya: bagaimana cara jQuery live () menyelesaikan masalah ini jika mereka tidak dapat mendeteksi perubahan DOM?
Kees C. Bakker

163
Saya tidak percaya bahwa pada 2010 Anda memiliki IE5.5 untuk menguji ini.
Oscar Godson

1
@JoshStodola Yang tebal juga mengganggu saya. Saya memutuskan untuk memperbaikinya.
Bojangles

1
Peristiwa mutasi sudah usang. Anda harus menggunakan MutationObserver. Saya telah menulis plugin untuk masalah seperti ini - github.com/AdamPietrasiak/jquery.initialize
pie6k

1
Bagaimana saya bisa mengaktifkan jquery onClick sebelum pengamat mutasi, yang menyala ketika tombol diklik dengan aksi bara? stackoverflow.com/questions/29216434/…
SuperUberDuper

219

Sejauh ini, inilah pendekatan pamungkas, dengan kode terkecil:

IE9 +, FF, Webkit:

Menggunakan MutationObserver dan kembali ke acara mutasi yang sudah tidak digunakan lagi jika diperlukan:
(Contoh di bawah ini hanya untuk perubahan DOM terkait dengan node yang ditambahkan atau dihapus)

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
tampaknya berfungsi cukup baik untuk node DOM baru. Bisakah kita mengadaptasinya untuk juga menangani perubahan dom node (setidaknya nilai / teks node DOM?)
Sebastien Lorber

8
@SebastienLorber - siapa "kita"? Anda, sebagai seorang programmer, dapat mengambil kode ini dan menggunakannya sesuai keinginan Anda. baca saja MDN tentang hal-hal yang dapat Anda amati DOM dan yang tidak dapat Anda amati.
vsync

2
Lewati mutations, observerparameter ke fungsi panggilan balik untuk kontrol lebih lanjut.
A1rPun

2
Ini banyak membantu saya, tetapi bagaimana saya "melepaskan" ini? Katakanlah saya ingin menyaksikan perubahan hanya sekali, tetapi lakukan ini beberapa kali? oberserveDOM = null jelas tidak akan berhasil ...
stiller_leser

Mengapa ini hanya berfungsi untuk ditambahkan / dihapus? Sepertinya acara mutasi mencakup lebih dari itu .. developer.mozilla.org/en-US/docs/Web/Guide/Events/…
f0ster

15

Baru-baru ini saya menulis sebuah plugin yang melakukan hal itu - jquery.initialize

Anda menggunakannya dengan cara yang sama seperti .eachfungsi

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

Perbedaannya .eachadalah - dibutuhkan pemilih Anda, dalam hal ini .some-elementdan menunggu elemen baru dengan pemilih ini di masa mendatang, jika elemen tersebut akan ditambahkan, ia akan diinisialisasi juga.

Dalam kasus kami, inisialisasi fungsi hanya mengubah warna elemen menjadi biru. Jadi jika kita akan menambahkan elemen baru (tidak peduli apakah dengan ajax atau bahkan inspektur F12 atau apa pun) seperti:

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

Plugin akan langsung melakukannya. Plugin juga memastikan satu elemen diinisialisasi hanya sekali. Jadi jika Anda menambahkan elemen, maka .detach()dari tubuh dan kemudian menambahkannya lagi, itu tidak akan diinisialisasi lagi.

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

Plugin didasarkan pada MutationObserver- itu akan bekerja pada IE9 dan 10 dengan dependensi seperti yang dirinci pada halaman readme .


Silakan, tambahkan ke npm.
thexpand

14

atau Anda cukup Buat acara Anda sendiri , yang berjalan di mana-mana

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

        });

Contoh lengkap http://jsfiddle.net/hbmaam/Mq7NX/


ini tidak sama ... metode yang dijelaskan di atas masih valid api.jquery.com/trigger
lefoy

11

Ini adalah contoh menggunakan MutationObserver dari Mozilla yang diadaptasi dari posting blog ini

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

Gunakan antarmuka MutationObserver seperti yang ditunjukkan di blog 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();

3
MutationObserver adalah JavaScript asli, bukan jQuery.
Benjamin

2

Bagaimana kalau memperpanjang jquery untuk ini?

   (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+ telah membangun dukungan untuk ini (saya dengar belum diuji).

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.