Sayfaya bir öğe eklendiğinde nasıl bildirim alabilirim?


139

Sayfaya bir DOM öğesi eklendiğinde çalışmamı seçtiğim bir işlev istiyorum. Bu bir tarayıcı uzantısı bağlamında, bu yüzden web sayfası benden bağımsız olarak çalışıyor ve kaynağını değiştiremiyorum. Burada benim seçeneklerim neler?

Sanırım, teoride, setInterval()elemanın varlığını sürekli olarak araştırmak ve eleman oradaysa eylemimi gerçekleştirmek için kullanabilirim , ancak daha iyi bir yaklaşıma ihtiyacım var.


Belirli bir öğeyi başka bir komut dosyasının sayfaya koyduğunu veya kaynak ne olursa olsun eklenen herhangi bir öğeyi kontrol etmeniz mi gerekiyor?
Jose Faeti

1
başka bir kişinin koduna hangi fonksiyonun eleman eklediğini biliyor musunuz, üzerine yazabilir ve özel bir olayı tetikleyen fazladan bir satır ekleyebilirsiniz?
jeffreydev

Yanıtlar:


86

Uyarı!

Bu cevap şimdi güncel değil. DOM Seviye 4 , kullanımdan kaldırılan mutasyon olayları için etkili bir yedek sağlayan MutationObserver'ı tanıttı . Burada sunulandan daha iyi bir çözüm için bu cevaba bakınız . Ciddi anlamda. DOM'u her 100 milisaniyede bir yoklama; CPU gücünü boşa harcar ve kullanıcılarınız sizden nefret eder.

Yana mutasyon olayları , 2012'de kullanımdan kaldırılmış ve onlar başkasının kodu tarafından eklendiği için eklenen elemanlar üzerinde denetim sahibi edildi tek seçenek sürekli onlar için kontrol etmektir.

function checkDOMChange()
{
    // check for any new element being inserted here,
    // or a particular node being modified

    // call the function again after 100 milliseconds
    setTimeout( checkDOMChange, 100 );
}

Bu işlev çağrıldığında, her 100 milisaniyede bir çalışır, bu da saniyenin 1 / 10'u (bir onda biri) olur. Gerçek zamanlı eleman gözlemine ihtiyacınız olmadığı sürece, bu yeterli olmalıdır.


1
jose, koşul gerçekten karşılandığında ayar zaman aşımını nasıl sonlandırırsın? yani, sayfanıza x saniye sonra yüklenen öğeyi buldunuz mu?
klewis

1
@blachawk setTimeout dönüş değerini bir değişkene atamanız gerekir; bunu daha sonra temizlemek için clearTimeout () parametresine paratemer olarak iletebilirsiniz.
Jose Faeti

3
@blackhawk: Bu cevabı gördüm ve + 1'ledim; ancak setTimeout () yalnızca bir kez çalıştığından, burada clearTimeout () kullanmanız gerekmediğinin farkındasınız! Bir 'if-else' sadece yeterlidir: eklenen öğeyi bulduğunuzda yürütmenin setTimeout () 'da geçmesine izin vermeyin.
1111161171159459134

Kullanmak için kullanışlı bir pratik rehber MutationObserver(): davidwalsh.name/mutationobserver-api
gfullam

4
Kirli. AS3'lere gerçekten eşdeğer bir şey yok Event.ADDED_TO_STAGEmu?
Bitterblue


19

Mutasyon olaylarının kullanımdan kaldırılması ve bunun ortaya çıkması arasında MutationObserver, DOM'a belirli bir öğe eklendiğinde bildirimde bulunmanın etkili bir yolu CSS3 animasyon olaylarından yararlanmaktı .

Blog yayınını alıntılamak için:

DOM düğümü ekleme olayı almak istediğiniz DOM öğelerini (CSS seçici seçiminiz yoluyla) hedefleyen bir CSS anahtar kare dizisi oluşturun. Nispeten iyi huylu ve az kullanılan bir css özelliği kullandım, klip Ben amaçlanan sayfa stilleri ile karışıklık önlemek için bir girişte anahat rengini kullandım - kod bir kez clip özelliğini hedefledi, ama artık 11 sürümünde IE'de canlandırılamıyor dedi, canlandırılabilecek herhangi bir özellik işe yarayacak, hangisini isterseniz onu seçin.

Daha sonra, düğüm eklemelerini işlemek için temsilci olarak kullandığım belge çapında bir animationstart dinleyicisi ekledim. Animation olayı, hangi animasyon karesi sırasının animasyonu başlattığını söyleyen animationName adlı bir özelliğe sahiptir. AnimationName özelliğinin, düğüm eklemeleri için eklediğiniz ana kare dizisi adıyla aynı olduğundan emin olun ve hazırsınız.


Bu en yüksek performanslı çözümdür ve yinelenen bir soruda bunun için bir kütüphaneden bahseden bir cevap var .
Dan Dascalescu

1
Yetersiz cevap.
Gökhan Kurt

Dikkatli. Milisaniye hassasiyet önemliyse, bu yaklaşım doğru olmaktan uzaktır. Bu jsbin bir satır içi geri arama ve kullanma arasında fazla 30ms fark olduğunu göstermektedir animationstart, jsbin.com/netuquralu/1/edit .
Gajus

Kurt'a katılıyorum, bu önemsiz.
Zabbu

13

livequeryJQuery için eklenti kullanabilirsiniz . Aşağıdaki gibi bir seçici ifade sağlayabilirsiniz:

$("input[type=button].removeItemButton").livequery(function () {
    $("#statusBar").text('You may now remove items.');
});

removeItemButtonSınıfın her düğmesi eklendiğinde, durum çubuğunda bir mesaj görünür.

Verimlilik açısından bundan kaçınmak isteyebilirsiniz, ancak her durumda kendi olay işleyicilerinizi oluşturmak yerine eklentiden yararlanabilirsiniz.

Tekrar ziyaret edilen cevap

Yukarıdaki cevap sadece bir öğenin DOM'a eklenti yoluyla eklendiğini tespit etmekti .

Bununla birlikte, büyük olasılıkla, bir jQuery.on()yaklaşım daha uygun olacaktır, örneğin:

$("#myParentContainer").on('click', '.removeItemButton', function(){
          alert($(this).text() + ' has been removed');
});

Örneğin, tıklamalara yanıt vermesi gereken dinamik içeriğiniz varsa, olayları kullanarak bir üst kapsayıcıya bağlamak en iyisidir jQuery.on.


11

ETA 24 Nis 17 Bunu biraz daha özlü hale getirdiği için bunu biraz async/ awaitbüyü ile basitleştirmek istedim :

Aynı söz verilen-gözlemlenebilir kullanımı:

const startObservable = (domNode) => {
  var targetNode = domNode;

  var observerConfig = {
    attributes: true,
    childList: true,
    characterData: true
  };

  return new Promise((resolve) => {
      var observer = new MutationObserver(function (mutations) {
         // For the sake of...observation...let's output the mutation to console to see how this all works
         mutations.forEach(function (mutation) {
             console.log(mutation.type);
         });
         resolve(mutations)
     });
     observer.observe(targetNode, observerConfig);
   })
} 

Arama işleviniz şu kadar basit olabilir:

const waitForMutation = async () => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {
      const results = await startObservable(someDomNode)
      return results
    } catch (err) { 
      console.error(err)
    }
}

Zaman aşımı eklemek isterseniz, burada gösterildiği gibi basit bir Promise.racedesen kullanabilirsiniz :

const waitForMutation = async (timeout = 5000 /*in ms*/) => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {

      const results = await Promise.race([
          startObservable(someDomNode),
          // this will throw after the timeout, skipping 
          // the return & going to the catch block
          new Promise((resolve, reject) => setTimeout(
             reject, 
             timeout, 
             new Error('timed out waiting for mutation')
          )
       ])
      return results
    } catch (err) { 
      console.error(err)
    }
}

orijinal

Bunu kütüphaneler olmadan yapabilirsiniz, ancak bazı ES6 öğelerini kullanmanız gerekir, bu nedenle uyumluluk sorunlarının farkında olun (yani, kitleniz çoğunlukla Amish, luddite veya daha kötüsü IE8 kullanıcılarıysa)

İlk olarak, bir gözlemci nesnesi oluşturmak için MutationObserver API'sini kullanacağız. Bu nesneyi bir söze sararızresolve() ve geri arama tetiklendiğinde (h / t davidwalshblog) mutasyonlarla ilgili david walsh blog makalesi :

const startObservable = (domNode) => {
    var targetNode = domNode;

    var observerConfig = {
        attributes: true,
        childList: true,
        characterData: true
    };

    return new Promise((resolve) => {
        var observer = new MutationObserver(function (mutations) {
            // For the sake of...observation...let's output the mutation to console to see how this all works
            mutations.forEach(function (mutation) {
                console.log(mutation.type);
            });
            resolve(mutations)
        });
        observer.observe(targetNode, observerConfig);
    })
} 

Sonra bir generator function. Bunları henüz kullanmadıysanız, kaçırıyorsunuz - ancak kısa bir özet: bir senkronizasyon işlevi gibi çalışır ve bir yield <Promise>ifade bulduğunda , vaatin engellenmesi için bekler yerine getirildi ( Jeneratörler bundan daha fazlasını yapıyorlar, ama biz burada ilgileniyoruz ).

// we'll declare our DOM node here, too
let targ = document.querySelector('#domNodeToWatch')

function* getMutation() {
    console.log("Starting")
    var mutations = yield startObservable(targ)
    console.log("done")
}

Jeneratörlerle ilgili zor bir kısım, normal bir işlev gibi 'geri dönmemeleri'. Bu yüzden jeneratörü normal bir fonksiyon gibi kullanabilmek için bir yardımcı fonksiyon kullanacağız. (yine, h / t - dwb )

function runGenerator(g) {
    var it = g(), ret;

    // asynchronously iterate over generator
    (function iterate(val){
        ret = it.next( val );

        if (!ret.done) {
            // poor man's "is it a promise?" test
            if ("then" in ret.value) {
                // wait on the promise
                ret.value.then( iterate );
            }
            // immediate value: just send right back in
            else {
                // avoid synchronous recursion
                setTimeout( function(){
                    iterate( ret.value );
                }, 0 );
            }
        }
    })();
}

Ardından, beklenen DOM mutasyonu gerçekleşmeden önce herhangi bir noktada çalıştırın runGenerator(getMutation).

Artık DOM mutasyonlarını senkronize bir kontrol akışına entegre edebilirsiniz. Ne kadar saçma.



3

Tam olarak bunu yapan bu eklentiye göz atın - jquery.initialize

Her işlev gibi çalışır, fark girdiğiniz seçiciyi alır ve ileride bu seçiciyle eşleşen yeni öğeleri izler ve başlatır.

Başlat şu şekilde görünür

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

Ancak şimdi .some-elementsayfada yeni öğe eşleme seçicisi görünecekse, anında başlatılacaktır.

Yeni öğenin eklenme şekli önemli değildir, geri aramalar vb. İle ilgilenmeniz gerekmez.

Dolayısıyla, aşağıdaki gibi yeni bir öğe eklerseniz:

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

anında başlatılacak.

Eklenti, MutationObserver


0

Saf bir javascript çözümü (olmadan jQuery):

const SEARCH_DELAY = 100; // in ms

// it may run indefinitely. TODO: make it cancellable, using Promise's `reject`
function waitForElementToBeAdded(cssSelector) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      if (element = document.querySelector(cssSelector)) {
        clearInterval(interval);
        resolve(element);
      }
    }, SEARCH_DELAY);
  });
}

console.log(await waitForElementToBeAdded('#main'));
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.