Come posso essere avvisato quando un elemento viene aggiunto alla pagina?


139

Voglio una funzione di mia scelta da eseguire quando un elemento DOM viene aggiunto alla pagina. Questo è nel contesto di un'estensione del browser, quindi la pagina web funziona indipendentemente da me e non posso modificarne l'origine. quali sono le mie opzioni?

Immagino che, in teoria, potrei semplicemente setInterval()cercare continuamente la presenza dell'elemento ed eseguire la mia azione se l'elemento è lì, ma ho bisogno di un approccio migliore.


Devi controllare per un particolare elemento un altro tuo script messo nella pagina o per qualsiasi elemento che viene aggiunto indipendentemente dalla fonte?
Jose Faeti,

1
sai quale funzione aggiunge l'elemento nel codice di qualcun altro, in tal caso puoi sovrascriverlo e aggiungere una riga aggiuntiva che attiva un evento personalizzato?
jeffreydev,

Risposte:


86

Avvertimento!

Questa risposta è ora obsoleta. DOM Level 4 ha introdotto MutationObserver , fornendo un sostituto efficace per gli eventi di mutazione deprecati. Vedi questa risposta per una soluzione migliore di quella presentata qui. Sul serio. Non eseguire il polling del DOM ogni 100 millisecondi; sprecherà la potenza della CPU e i tuoi utenti ti odieranno.

Poiché gli eventi di mutazione sono stati deprecati nel 2012 e non hai alcun controllo sugli elementi inseriti perché sono aggiunti dal codice di qualcun altro, l'unica opzione è quella di verificarli continuamente.

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

Una volta chiamata questa funzione, verrà eseguita ogni 100 millisecondi, ovvero 1/10 (un decimo) di secondo. A meno che non sia necessaria l'osservazione degli elementi in tempo reale, dovrebbe essere sufficiente.


1
jose, come si termina il settimeout quando la condizione è effettivamente soddisfatta? cioè, hai trovato l'elemento che alla fine è stato caricato x secondi dopo nella tua pagina?
Klewis

1
@blachawk devi assegnare il valore restituito setTimeout a una variabile, che puoi in seguito passare come paratemer a clearTimeout () per cancellarlo.
Jose Faeti,

3
@blackhawk: ho appena visto quella risposta e +1; ma ti renderai conto che in realtà non è necessario utilizzare clearTimeout () qui, poiché setTimeout () viene eseguito solo una volta! Un 'if-else' è appena sufficiente: non lasciare che l'esecuzione passi su setTimeout () una volta trovato l'elemento inserito.
1111161171159459134

Un'utile guida pratica all'utilizzo MutationObserver(): davidwalsh.name/mutationobserver-api
gfullam

4
È sporco. Non esiste davvero un "equivalente" di AS3 Event.ADDED_TO_STAGE?
Bitterblue,


19

Tra la deprecazione degli eventi di mutazione e l'emergere di MutationObserver, un modo efficace per essere avvisato quando un elemento specifico è stato aggiunto al DOM era quello di sfruttare gli eventi di animazione CSS3 .

Per citare il post del blog:

Imposta una sequenza di fotogrammi chiave CSS che abbia come target (tramite la tua scelta del selettore CSS) qualunque elemento DOM per cui desideri ricevere un evento di inserimento del nodo DOM. Ho usato una proprietà css relativamente benigna e poco usata, clip Ho usato il colore di contorno nel tentativo di evitare di fare confusione con gli stili di pagina previsti: il codice una volta aveva come target la proprietà della clip, ma non è più animabile in IE dalla versione 11. Quello detto, qualsiasi proprietà che può essere animata funzionerà, scegli quella che ti piace.

Successivamente ho aggiunto un listener di animazioni a livello di documento che utilizzo come delegato per elaborare gli inserimenti dei nodi. L'evento di animazione ha una proprietà chiamata animationName che indica quale sequenza di fotogrammi chiave ha dato il via all'animazione. Assicurati solo che la proprietà animationName sia la stessa del nome della sequenza del fotogramma chiave che hai aggiunto per gli inserimenti di nodi e sei pronto.


Questa è la soluzione più performante, e c'è una risposta in una domanda duplicata che menziona una libreria per questo.
Dan Dascalescu,

1
Risposta sottovalutata.
Gökhan Kurt

Attento. Se la precisione in millisecondi è importante, questo approccio è tutt'altro che accurato. Questo jsbin dimostra che esiste una differenza di oltre 30 ms tra un callback inline e l'utilizzo animationstart, jsbin.com/netuquralu/1/edit .
Gajus,

Sono d'accordo con Kurt, questo è sottovalutato.
Zabbu,

13

Puoi usare il livequeryplugin per jQuery. È possibile fornire un'espressione di selezione come:

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

Ogni volta che removeItemButtonviene aggiunto un pulsante di una classe, viene visualizzato un messaggio in una barra di stato.

In termini di efficienza potresti voler evitare questo, ma in ogni caso potresti sfruttare il plugin invece di creare i tuoi gestori di eventi.

Risposta rivisitata

La risposta sopra era intesa solo a rilevare che un elemento è stato aggiunto al DOM tramite il plugin.

Tuttavia, molto probabilmente, un jQuery.on()approccio sarebbe più appropriato, ad esempio:

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

Se disponi di contenuto dinamico che dovrebbe rispondere ai clic, ad esempio, è consigliabile associare eventi a un contenitore padre utilizzando jQuery.on.


11

ETA 24 apr 17 Volevo semplificare un po 'questo con un po' async/ awaitmagia, in quanto lo rende molto più conciso:

Utilizzando lo stesso promisificato-osservabile:

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

La tua funzione di chiamata può essere semplice come:

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)
    }
}

Se si desidera aggiungere un timeout, è possibile utilizzare un Promise.racemodello semplice , come dimostrato qui :

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)
    }
}

Originale

Puoi farlo senza librerie, ma dovresti usare alcune cose ES6, quindi sii consapevole dei problemi di compatibilità (ad esempio, se il tuo pubblico è principalmente Amish, luddite o, peggio, utenti IE8)

Innanzitutto, utilizzeremo l' API MutationObserver per costruire un oggetto osservatore. Avvolgeremo questo oggetto in una promessa, e resolve()quando viene attivato il callback (h / t davidwalshblog) david walsh articolo del blog sulle mutazioni :

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

Quindi, creeremo un generator function. Se non li hai ancora usati, ti stai perdendo, ma una breve sinossi è: funziona come una funzione di sincronizzazione e quando trova yield <Promise>un'espressione, attende in modo non bloccante che la promessa sia appagato (i generatori fanno più di questo, ma questo è ciò a cui siamo interessati qui ).

// 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")
}

Una parte difficile dei generatori è che non "ritornano" come una normale funzione. Quindi, useremo una funzione di aiuto per essere in grado di usare il generatore come una normale funzione. (di nuovo, da h / t a 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 );
            }
        }
    })();
}

Quindi, in qualsiasi momento prima che possa verificarsi la mutazione DOM prevista, esegui semplicemente runGenerator(getMutation).

Ora puoi integrare le mutazioni DOM in un flusso di controllo in stile sincrono. Che ne dici.



3

Dai un'occhiata a questo plugin che lo fa esattamente - jquery.initialize

Funziona esattamente come la funzione .each, la differenza è che prende il selettore che hai inserito e cerca nuovi elementi aggiunti in futuro corrispondenti a questo selettore e inizializzali

Inizializza assomiglia a questo

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

Ma ora se un nuovo .some-elementselettore di corrispondenza elemento verrà visualizzato sulla pagina, verrà immediatamente inizializzato.

Il modo in cui viene aggiunto un nuovo elemento non è importante, non è necessario preoccuparsi di eventuali callback ecc.

Quindi se aggiungi un nuovo elemento come:

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

verrà immediatamente inizializzato.

Il plugin si basa su MutationObserver


0

Una soluzione javascript pura (senza 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'));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.