DOM'daki değişiklikleri algılama


242

Html bazı div veya girdi eklendiğinde bir işlevi yürütmek istiyorum. Mümkün mü?

Örneğin, bir metin girişi eklenir, ardından işlev çağrılmalıdır.


1
Bazı üçüncü taraf komut dosyaları DOM'a düğüm eklemedikçe, bu gerekli değildir.
Justin Johnson



4
@JustinJohnson JS kodunu enjekte eden bir krom uzantısı oluşturuyorsanız, yararlıdır.
FluorescentGreen5

Yanıtlar:


206

2015 güncellemesi, yeni MutationObservermodern tarayıcılar tarafından desteklenmektedir:

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

Daha eski olanları desteklemeniz gerekiyorsa, aşağıdaki bu 5 (!) Yaşındaki cevapta belirtilenler gibi diğer yaklaşımlara geri dönmeyi deneyebilirsiniz . Ejderhalar var. Zevk almak :)


Başka biri belgeyi değiştiriyor mu? Çünkü değişiklikler üzerinde tam kontrole sahipseniz, sadece domChangedbir işlev veya özel etkinlikle kendi API'nizi oluşturmanız ve her şeyi değiştirdiğiniz her yerde tetikleyin / arayın.

DOM Düzey-2 sahiptir Mutasyon olay türleri , ancak IE eski sürümünü bunu desteklemez. Mutasyon olaylarının DOM3 Events spesifikasyonunda kullanımdan kaldırıldığını ve performans cezası bulunduğunu unutmayın .

onpropertychangeIE'de mutasyon olayını taklit etmeye çalışabilirsiniz (ve eğer mevcut değilse kaba kuvvet yaklaşımına geri dönebilirsiniz).

Bir İçin Tam domChange bir aralık aşırı öldürmek olabilir. Tüm belgenin geçerli durumunu kaydetmeniz ve her öğenin her özelliğinin aynı olup olmadığını incelemeniz gerektiğini düşünün.

Belki sadece elemanlarla ve sıralarıyla ilgileniyorsanız (sorunuzda belirtildiği gibi), bir getElementsByTagName("*")işe yarayabilir. Bir öğe eklerseniz, bir öğe kaldırırsanız, öğeleri değiştirirseniz veya belgenin yapısını değiştirirseniz bu otomatik olarak tetiklenir.

Bir kavram kanıtı yazdım:

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

Kullanımı:

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

IE 5.5+, FF 2+, Chrome, Safari 3+ ve Opera 9.6+ üzerinde çalışır


4
Merak: jQuery live () bir DOM değişikliğini algılayamazlarsa bu sorunu nasıl çözer?
Kees C.Bakker

163
2010 yılında bunu test etmek için IE5.5'iniz olduğuna inanamıyorum.
Oscar Godson

1
@JoshStodola Cesur beni de rahatsız ediyordu. Düzeltmeye karar verdim.
Bojangles

1
Mutasyon olayları kullanımdan kaldırıldı. MutationObserver kullanmalısınız. Eklentimi
pie6k

1
Bir köz eylemiyle bir düğmeye tıklandığında tetiklenen bir mutasyon gözlemcisinden önce ateş etmeyi nasıl tıklayabilirim? stackoverflow.com/questions/29216434/…
SuperUberDuper

219

Şimdiye kadar en küçük kodla nihai yaklaşım budur:

IE9 +, FF, Webkit:

Kullanma MutationObserver ve artık düşen geri mutasyon olayları : Gerekirse
(sadece DOM düğümleri ekteki veya kaldırılır De¤ifltirilmifltir ise aşağıdaki Örnek)

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
yeni DOM düğümleri için oldukça iyi çalışıyor gibi görünüyor. Dom düğümü değişikliklerini de (en azından DOM düğümü değerleri / metni) ele almak için uyarlayabilir miyiz?
Sebastien Lorber

8
@SebastienLorber - "biz" kim? bir programcı olarak bu kodu alıp istediğiniz gibi kullanabilirsiniz. MDN'de DOM için hangi şeyleri gözlemleyebileceğinizi ve hangilerini göremeyeceğinizi okuyun.
vsync

2
Geçiş mutations, observerfazla kontrol için geri arama işlevine parametreleri.
A1rPun

2
Bu bana çok yardımcı oldu, ama bunu nasıl "çözerim"? Bir değişikliği yalnızca bir kez izlemek istediğimi, ancak bunu birden çok kez mi yaptığımı varsayalım? oberserveDOM = null belli ki çalışmaz ...
stiller_leser

Bu neden yalnızca eklenen / kaldırılanlar için işe yarar? Mutasyon olayları bundan daha fazlasını kapsıyor gibi görünüyor. Developer.mozilla.org/en-US/docs/Web/Guide/Events/…
f0ster

15

Son zamanlarda tam olarak bunu yapan bir eklenti yazdım - jquery.initialize

.eachFonksiyonla aynı şekilde kullanırsınız

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

Farkı .each- seçicinizi alır, bu durumda .some-elementve gelecekte bu seçiciyle yeni öğeler bekleyin, eğer böyle bir öğe eklenirse, ilk olarak da başlatılacaktır.

Bizim durumumuzda başlatma fonksiyonu sadece eleman rengini mavi olarak değiştirir. Dolayısıyla, yeni bir öğe ekleyeceksek (ajax veya F12 denetçisi ya da herhangi bir şey olsa da):

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

Eklenti anında başlatacaktır. Eklenti ayrıca bir öğenin yalnızca bir kez başlatılmasını sağlar. Öyleyse, öğeyi eklerseniz, o .detach()zaman gövdeden sonra tekrar eklerseniz, yeniden başlatılmaz.

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

Eklenti dayanmaktadır MutationObserver- Benio sayfasında ayrıntılı olarak bağımlılıklar ile IE9 ve 10 üzerinde çalışacaktır .


Lütfen npm'ye ekleyin.
thexpand

14

ya da her yerde çalışan kendi etkinliğinizi kolayca oluşturabilirsiniz

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

        });

Tam örnek http://jsfiddle.net/hbmaam/Mq7NX/


bu aynı değil ... yukarıda açıklanan yöntem hala geçerlidir api.jquery.com/trigger
lefoy

11

Bu kullanarak bir örnektir MutationObserver dan Mozilla bu uyarlanan blog post

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 arayüzünü Gabriele Romanato blogunda gösterildiği gibi kullanın

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 jQuery değil, yerel JavaScript'tir.
Benjamin

2

Bunun için bir jquery uzatmaya ne dersiniz?

   (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+ bunun için destek oluşturdu (test edilmediğini duydum).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.