Existe-t-il un écouteur de changement DOM JavaScript / jQuery?

402

Essentiellement, je veux qu'un script s'exécute lorsque le contenu d'un DIVchangement. Étant donné que les scripts sont séparés (script de contenu dans l'extension Chrome et le script de page Web), j'ai besoin d'un moyen simplement d'observer les changements dans l'état DOM. Je pourrais mettre en place un sondage mais cela semble bâclé.

Fletcher Moore
la source

Réponses:

488

Pendant longtemps, les événements de mutation DOM3 étaient la meilleure solution disponible, mais ils ont été déconseillés pour des raisons de performances. Les observateurs de mutation DOM4 remplacent les événements de mutation DOM3 obsolètes. Ils sont actuellement implémentés dans les navigateurs modernes comme MutationObserver(ou comme préfixés par le fournisseur WebKitMutationObserverdans les anciennes versions de Chrome):

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function(mutations, observer) {
    // fired when a mutation occurs
    console.log(mutations, observer);
    // ...
});

// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
  subtree: true,
  attributes: true
  //...
});

Cet exemple écoute les modifications DOM sur documentet toute sa sous-arborescence, et il se déclenche sur les modifications apportées aux attributs d'élément ainsi que les modifications structurelles. Le projet de spécification contient une liste complète des propriétés d'écouteur de mutation valides :

childList

  • Définissez truesi des mutations sur les enfants de la cible doivent être observées.

les attributs

  • Définissez truesi des mutations des attributs de la cible doivent être observées.

characterData

  • Définissez truesi des mutations des données de la cible doivent être observées.

sous-arbre

  • Définissez truesi les mutations non seulement pour la cible, mais aussi pour les descendants de la cible doivent être observées.

attributeOldValue

  • La valeur trueif attributesest définie sur true et la valeur d'attribut de la cible avant que la mutation ne soit enregistrée.

characterDataOldValue

  • Défini sur trueif characterDataest défini sur true et les données de la cible avant que la mutation ne soit enregistrée.

attributeFilter

  • Définissez une liste de noms locaux d'attributs (sans espace de noms) si toutes les mutations d'attributs ne doivent pas être observées.

(Cette liste est à jour en avril 2014; vous pouvez vérifier les spécifications pour tout changement.)

absides
la source
3
@AshrafBashir Je vois que l'échantillon fonctionne correctement dans Firefox 19.0.2: je vois que je suis ([{}])connecté à la console, ce qui montre ce qui est attendu MutationRecordlorsque je clique dessus. Veuillez vérifier à nouveau, car il pourrait s'agir d'une défaillance technique temporaire dans JSFiddle. Je ne l'ai pas encore testé dans IE, car je n'ai pas IE 10, qui est actuellement la seule version à prendre en charge les événements de mutation.
apsillers
1
Je viens de publier une réponse qui fonctionne dans IE10 + et surtout n'importe quoi d'autre.
naugtur
1
La spécification ne semble pas avoir de boîte verte qui répertorie plus les options d'observateur de mutation. Il énumère les options de la section 5.3.1 et les décrit juste un peu plus loin.
LS
2
@LS Merci, j'ai mis à jour le lien, supprimé le bit sur la boîte verte et édité toute la liste dans ma réponse (juste en cas de future pourriture du lien).
apsillers
211

Éditer

Cette réponse est désormais obsolète. Voir la réponse des apsillers .

Comme il s'agit d'une extension Chrome, vous pouvez tout aussi bien utiliser l'événement DOM standard - DOMSubtreeModified. Voir le support de cet événement sur tous les navigateurs. Il est pris en charge dans Chrome depuis 1.0.

$("#someDiv").bind("DOMSubtreeModified", function() {
    alert("tree changed");
});

Voir un exemple de travail ici .

Anurag
la source
J'ai remarqué que cet événement peut être déclenché même après certains sélecteurs. J'enquête actuellement.
Peder Rice,
9
w3.org/TR/DOM-Level-3-Events/#event-type-DOMSubtreeModified dit que cet événement est obsolète, que pourrions-nous utiliser à la place?
Maslow
2
@ Maslow- Il n'y en a pas! stackoverflow.com/questions/6659662/…
Sam Rueby
4
Il y a github.com/joelpurra/jquery-mutation-summary qui le résout essentiellement pour les utilisateurs de jquery.
Capi Etheriel
1
Fonctionne toujours si vous créez des extensions Chrome. inestimable.
concept47
49

De nombreux sites utilisent AJAX pour ajouter / afficher / modifier le contenu de manière dynamique. Parfois, il est utilisé à la place de la navigation sur site, donc l'URL actuelle est modifiée par programme et les scripts de contenu ne sont pas automatiquement exécutés par le navigateur dans ce cas, car la page n'est pas entièrement récupérée du serveur distant.


Méthodes JS habituelles de détection des modifications de page disponibles dans un script de contenu .

  • MutationObserver ( docs ) pour détecter littéralement les changements DOM:

  • Écouteur d'événements pour les sites qui signalent un changement de contenu en envoyant un événement DOM:

  • Vérification périodique du DOM via setInterval :
    Évidemment, cela ne fonctionnera que dans les cas où vous attendez qu'un élément spécifique identifié par son identifiant / sélecteur apparaisse, et il ne vous permettra pas de détecter universellement le nouveau contenu ajouté dynamiquement à moins que vous n'inventiez une sorte d'empreinte digitale le contenu existant.

  • API d'historique de masquage à l' intérieur d'un script DOM injecté :

    document.head.appendChild(document.createElement('script')).text = '(' +
        function() {
            // injected DOM script is not a content script anymore, 
            // it can modify objects and functions of the page
            var _pushState = history.pushState;
            history.pushState = function(state, title, url) {
                _pushState.call(this, state, title, url);
                window.dispatchEvent(new CustomEvent('state-changed', {detail: state}));
            };
            // repeat the above for replaceState too
        } + ')(); this.remove();'; // remove the DOM script element
    
    // And here content script listens to our DOM script custom events
    window.addEventListener('state-changed', function(e) {
        console.log('History state changed', e.detail, location.hash);
        doSomething();
    });
    
  • Écoute de hashchange , événements popstate :

    window.addEventListener('hashchange', function(e) {
        console.log('URL hash changed', e);
        doSomething();
    });
    window.addEventListener('popstate', function(e) {
        console.log('State changed', e);
        doSomething();
    });
    



Spécifique aux extensions: détecte les changements d'URL dans une page d' arrière - plan / événement .

Il existe des API avancées pour travailler avec la navigation: webNavigation , webRequest , mais nous utiliserons un simple écouteur d'événements chrome.tabs.onUpdated qui envoie un message au script de contenu:

  • manifest.json:
    déclarer la page d'arrière-plan / événement
    déclarer l' autorisation d' ajouter un script de contenu .
    "tabs"

  • background.js

    var rxLookfor = /^https?:\/\/(www\.)?google\.(com|\w\w(\.\w\w)?)\/.*?[?#&]q=/;
    chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
        if (rxLookfor.test(changeInfo.url)) {
            chrome.tabs.sendMessage(tabId, 'url-update');
        }
    });
    
  • content.js

    chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
        if (msg === 'url-update') {
            doSomething();
        }
    });
    
wOxxOm
la source
29

Une autre approche selon la façon dont vous changez le div. Si vous utilisez JQuery pour modifier le contenu d'un div avec sa méthode html (), vous pouvez étendre cette méthode et appeler une fonction d'enregistrement chaque fois que vous mettez du html dans un div.

(function( $, oldHtmlMethod ){
    // Override the core html method in the jQuery object.
    $.fn.html = function(){
        // Execute the original HTML method using the
        // augmented arguments collection.

        var results = oldHtmlMethod.apply( this, arguments );
        com.invisibility.elements.findAndRegisterElements(this);
        return results;

    };
})( jQuery, jQuery.fn.html );

Nous interceptons simplement les appels à html (), appelons une fonction d'enregistrement avec cela, qui dans le contexte fait référence à l'élément cible obtenant un nouveau contenu, puis nous passons l'appel à la fonction jquery.html () d'origine. N'oubliez pas de renvoyer les résultats de la méthode html () d'origine, car JQuery l'attend pour le chaînage de méthode.

Pour plus d'informations sur la substitution et l'extension de méthode, consultez http://www.bennadel.com/blog/2009-Using-Self-Executing-Function-Arguments-To-Override-Core-jQuery-Methods.htm , qui est l'endroit où J'ai bercé la fonction de fermeture. Consultez également le tutoriel des plugins sur le site de JQuery.

Zac Imboden
la source
7

En plus des outils "bruts" fournis par l' MutationObserverAPI , il existe des bibliothèques "pratiques" pour travailler avec les mutations DOM.

Considérez: MutationObserver représente chaque changement DOM en termes de sous-arbres. Donc, si vous attendez, par exemple, qu'un certain élément soit inséré, il peut être au plus profond des enfants de mutations.mutation[i].addedNodes[j].

Un autre problème est lorsque votre propre code, en réaction aux mutations, change de DOM - vous voulez souvent le filtrer.

Une bonne bibliothèque pratique qui résout ces problèmes est mutation-summary(avertissement: je ne suis pas l'auteur, juste un utilisateur satisfait), qui vous permet de spécifier des requêtes de ce qui vous intéresse et d'obtenir exactement cela.

Exemple d'utilisation de base des documents:

var observer = new MutationSummary({
  callback: updateWidgets,
  queries: [{
    element: '[data-widget]'
  }]
});

function updateWidgets(summaries) {
  var widgetSummary = summaries[0];
  widgetSummary.added.forEach(buildNewWidget);
  widgetSummary.removed.forEach(cleanupExistingWidget);
}
Xan
la source