Existe um ouvinte de alteração de JavaScript / jQuery DOM?

402

Basicamente, quero que um script seja executado quando o conteúdo de uma DIValteração. Como os scripts são separados (script de conteúdo na extensão do Chrome e no script da página da web), preciso de uma maneira simplesmente observar as alterações no estado do DOM. Eu poderia configurar a pesquisa, mas isso parece desleixado.

Fletcher Moore
fonte

Respostas:

488

Por um longo tempo, os eventos de mutação DOM3 foram a melhor solução disponível, mas foram preteridos por motivos de desempenho. Os observadores de mutação DOM4 substituem os eventos de mutação DOM3 descontinuados. Atualmente, eles são implementados em navegadores modernos como MutationObserver(ou como prefixo do fornecedor WebKitMutationObservernas versões antigas do 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
  //...
});

Este exemplo escuta as alterações do DOM em documenttoda a sua subárvore e aciona as alterações nos atributos do elemento, bem como as mudanças estruturais. A especificação de rascunho possui uma lista completa de propriedades válidas do ouvinte de mutação :

childList

  • Defina como truese as mutações nos filhos do alvo devem ser observadas.

atributos

  • Defina como truese as mutações nos atributos do alvo devem ser observadas.

characterData

  • Defina como truese as mutações nos dados do alvo devem ser observadas.

subárvore

  • Defina como truese as mutações não apenas atingirem o alvo, mas também os descendentes do alvo devem ser observados.

attributeOldValue

  • Defina como trueif attributescomo true e o valor do atributo do destino antes que a mutação precise ser registrada.

characterDataOldValue

  • Defina como trueif characterDatacomo true e os dados do destino antes que a mutação precise ser registrada.

attributeFilter

  • Defina como uma lista de nomes locais de atributos (sem espaço para nome) se nem todas as mutações de atributo precisarem ser observadas.

(Esta lista está atualizada em abril de 2014; você pode verificar a especificação para quaisquer alterações.)

apsillers
fonte
3
@AshrafBashir Vejo o exemplo funcionando bem no Firefox 19.0.2: vejo o ([{}])logon no console, que mostra o esperado MutationRecordquando clico nele. Verifique novamente, pois pode ter sido uma falha técnica temporária no JSFiddle. Ainda não testei no IE, pois não tenho o IE 10, que atualmente é a única versão para suportar eventos de mutação.
Apsillers
11
Acabei de publicar uma resposta que funciona no IE10 + e principalmente qualquer outra coisa.
naugtur
11
A especificação parece não ter uma caixa verde que lista as opções de observador de mutação por mais tempo. Ele lista as opções na seção 5.3.1 e as descreve apenas um pouco mais abaixo disso.
LS
2
@LS Obrigado, atualizei o link, removi a parte verde da caixa e editei a lista inteira em minha resposta (apenas no caso de futuras rotações de links).
Apsillers
211

Editar

Esta resposta está agora obsoleta. Veja a resposta dos apsillers .

Como se trata de uma extensão do Chrome, você também pode usar o evento DOM padrão - DOMSubtreeModified. Veja o suporte para este evento nos navegadores. É suportado no Chrome desde 1.0.

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

Veja um exemplo de trabalho aqui .

Anurag
fonte
Percebi que esse evento pode ser acionado mesmo após determinados seletores. Atualmente estou investigando.
Peder Rice
9
O w3.org/TR/DOM-Level-3-Events/#event-type-DOMSubtreeModified diz que esse evento foi descontinuado. O que usaríamos ?
Maslow
2
@ Maslow- Não há! stackoverflow.com/questions/6659662/…
Sam Rueby
4
Existe o github.com/joelpurra/jquery-mutation-summary que basicamente o resolve para os usuários do jquery.
Capi Etheriel 01/10/12
11
Ainda funciona se você estiver criando extensões do Chrome. inestimável.
concept47
49

Muitos sites usam o AJAX para adicionar / mostrar / alterar conteúdo dinamicamente. Às vezes, é usado em vez da navegação no site; portanto, o URL atual é alterado de forma programática e, nesse caso, os scripts de conteúdo não são executados automaticamente pelo navegador, pois a página não é obtida totalmente do servidor remoto.


Métodos JS comuns para detectar alterações de página disponíveis em um script de conteúdo .

  • MutationObserver ( docs ) para detectar literalmente alterações no DOM:

  • Ouvinte de evento para sites que sinalizam alteração de conteúdo enviando um evento DOM:

  • Verificação periódica do DOM via setInterval :
    Obviamente, isso funcionará apenas nos casos em que você esperar que um elemento específico identificado por seu ID / seletor apareça e não permitirá que você detecte universalmente novo conteúdo adicionado dinamicamente, a menos que invente algum tipo de impressão digital o conteúdo existente.

  • API de histórico de cloaking dentro de um script DOM injetado :

    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();
    });
  • Ouvindo hashchange , popstate eventos:

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



Específico para extensões: detecte alterações de URL em uma página de segundo plano / evento .

Há uma API avançada para trabalhar com a navegação: webNavigation , webRequest , mas usaremos o chrome.tabs.onUpdated listener de evento simples que envia uma mensagem para o script de conteúdo:

  • manifest.json:
    declarar página de segundo plano / evento
    declarar permissão de inclusão de script de conteúdo .
    "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
fonte
29

Outra abordagem, dependendo de como você está alterando a div. Se você estiver usando o JQuery para alterar o conteúdo de uma div com seu método html (), poderá estender esse método e chamar uma função de registro sempre que colocar o html em uma 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 );

Nós apenas interceptamos as chamadas para html (), chamamos uma função de registro com isso, que no contexto se refere ao elemento de destino obtendo novo conteúdo, depois passamos a chamada para a função original jquery.html (). Lembre-se de retornar os resultados do método html () original, porque o JQuery espera isso para o encadeamento de métodos.

Para obter mais informações sobre substituição e extensão de método, consulte http://www.bennadel.com/blog/2009-Using-Self-Executing-Function-Arguments-To-Override-Core-jQuery-Methods.htm , que é onde Eu usei a função de fechamento. Verifique também o tutorial de plugins no site do JQuery.

Zac Imboden
fonte
7

Além das ferramentas "brutas" fornecidas pela MutationObserverAPI , existem bibliotecas de "conveniência" para trabalhar com mutações do DOM.

Considere: MutationObserver representa cada alteração do DOM em termos de subárvores. Portanto, se você está, por exemplo, esperando que um determinado elemento seja inserido, ele pode estar dentro dos filhos de mutations.mutation[i].addedNodes[j].

Outro problema é quando seu próprio código, em reação a mutações, altera o DOM - você geralmente deseja filtrá-lo.

Uma boa biblioteca de conveniência que resolve esses problemas é mutation-summary(exoneração de responsabilidade: não sou o autor, apenas um usuário satisfeito), que permite especificar consultas sobre o que lhe interessa e obter exatamente isso.

Exemplo de uso básico dos documentos:

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
fonte