简体   繁体   English

检测 DOM 中的变化

[英]Detect changes in the DOM

I want to execute a function when some div or input are added to the html.当某些 div 或输入添加到 html 时,我想执行 function。 Is this possible?这可能吗?

For example, a text input is added, then the function should be called.例如,添加一个文本输入,则应调用 function。

Ultimate approach so far, with smallest code:迄今为止的终极方法,代码最少:

(IE11+, FF, Webkit) (IE11+, FF, Webkit)

Using MutationObserver and falling back to the deprecated Mutation events if needed:如果需要,使用MutationObserver并回退到已弃用的Mutation 事件
(Example below if only for DOM changes concerning nodes appended or removed) (下面的示例,如果仅针对有关附加或删除节点的 DOM 更改)

 var observeDOM = (function(){ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; return function( obj, callback ){ if( !obj || obj.nodeType !== 1 ) return; if( MutationObserver ){ // define a new observer var mutationObserver = new MutationObserver(callback) // have the observer observe foo for changes in children mutationObserver.observe( obj, { childList:true, subtree:true }) return mutationObserver } // browser support fallback 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>

2015 update, new MutationObserver is supported by modern browsers: 2015 更新,现代浏览器支持新的MutationObserver

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

If you need to support older ones, you may try to fall back to other approaches like the ones mentioned in this 5 (!) year old answer below.如果您需要支持较旧的方法,您可以尝试使用其他方法,例如下面这个5 (!) 岁的答案中提到的方法。 There be dragons.有龙。 Enjoy :)享受 :)


Someone else is changing the document?其他人正在更改文档? Because if you have full control over the changes you just need to create your own domChanged API - with a function or custom event - and trigger/call it everywhere you modify things.因为如果您可以完全控制更改,您只需要创建自己的domChanged API - 使用函数或自定义事件 - 并在您修改内容的任何地方触发/调用它。

The DOM Level-2 has Mutation event types , but older version of IE don't support it. DOM Level-2 有Mutation event types ,但旧版本的 IE 不支持它。 Note that the mutation events are deprecated in the DOM3 Events spec and have a performance penalty .请注意, 更改事件在 DOM3 事件规范中已被 弃用,并且会降低性能

You can try to emulate mutation event with onpropertychange in IE (and fall back to the brute-force approach if non of them is available).您可以尝试在 IE 中使用onpropertychange模拟突变事件(如果没有可用的方法,则退回到蛮力方法)。

For a full domChange an interval could be an over-kill.对于完整的domChange,间隔可能会过大。 Imagine that you need to store the current state of the whole document, and examine every element's every property to be the same.想象一下,您需要存储整个文档的当前状态,并检查每个元素的每个属性是否相同。

Maybe if you're only interested in the elements and their order (as you mentioned in your question), a getElementsByTagName("*") can work.也许如果您只对元素及其顺序感兴趣(正如您在问题中提到的), getElementsByTagName("*")可以工作。 This will fire automatically if you add an element, remove an element, replace elements or change the structure of the document.如果您添加元素、删除元素、替换元素或更改文档结构,这将自动触发。

I wrote a proof of concept:我写了一个概念证明:

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

Usage:用法:

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

This works on IE 5.5+, FF 2+, Chrome, Safari 3+ and Opera 9.6+这适用于 IE 5.5+、FF 2+、Chrome、Safari 3+ 和 Opera 9.6+

The following example was adapted from Mozilla Hacks' blog post and is using MutationObserver .以下示例改编自 Mozilla Hacks 的博客文章,并使用MutationObserver

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

Browser support: Chrome 18+, Firefox 14+, IE 11+, Safari 6+浏览器支持:Chrome 18+、Firefox 14+、IE 11+、Safari 6+

or you can simply Create your own event , that run everywhere或者你可以简单地创建你自己的事件,到处运行

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

        });

Full example http://jsfiddle.net/hbmaam/Mq7NX/完整示例http://jsfiddle.net/hbmaam/Mq7NX/

I have recently written a plugin that does exactly that - jquery.initialize我最近写了一个插件就是这样做的 - jquery.initialize

You use it the same way as .each function您可以像使用.each函数一样使用它

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

The difference from .each is - it takes your selector, in this case .some-element and wait for new elements with this selector in the future, if such element will be added, it will be initialized too..each的不同之处在于 - 它需要您的选择器,在这种情况下是.some-element并在将来等待具有此选择器的新元素,如果将添加此类元素,它也会被初始化。

In our case initialize function just change element color to blue.在我们的例子中,初始化函数只是将元素颜色更改为蓝色。 So if we'll add new element (no matter if with ajax or even F12 inspector or anything) like:因此,如果我们要添加新元素(无论是使用 ajax 还是 F12 检查器或其他任何东西),例如:

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

Plugin will init it instantly.插件会立即初始化它。 Also plugin makes sure one element is initialized only once.插件还确保一个元素只初始化一次。 So if you add element, then .detach() it from body and then add it again, it will not be initialized again.因此,如果您添加元素,然后将.detach()从主体中删除,然后再次添加,则不会再次初始化。

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

Plugin is based on MutationObserver - it will work on IE9 and 10 with dependencies as detailed on the readme page .插件基于MutationObserver - 它将在 IE9 和 10 上运行,并具有自述文件页面上详述的依赖项。

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

Complete explanations: https://stackoverflow.com/a/11546242/6569224完整解释: https : //stackoverflow.com/a/11546242/6569224

Use the MutationObserver interface as shown in Gabriele Romanato's blog使用MutationObserver界面,如 Gabriele Romanato 的博客中所示

Chrome 18+, Firefox 14+, IE 11+, Safari 6+ 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();

How about extending a jquery for this?为此扩展一个jquery怎么样?

   (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+ has built support for this(I have heard not tested). Jquery 1.9+ 已经建立了对此的支持(我听说没有测试过)。

8 years later, here is my solution using MutationObserver and RxJS 8 年后,这是我使用MutationObserverRxJS解决方案

observeDOMChange(document.querySelector('#dom-changes-here'))
  .subscribe(val => log('DOM-change detected'));

The main difference from the other approaches is to fire a CustomEvent when DOM changes, and listen to the event debounced to execute user logic efficiently with the following features;与其他方法的主要区别是在 DOM 更改时触发CustomEvent ,并通过以下功能侦听CustomEvent事件以有效执行用户逻辑;

  • Debounce consecutive DOM changes to prevent too many executions去抖连续的 DOM 更改以防止执行过多
  • Stop watching after the given time在给定时间后停止观看
  • Removes event listeners/subscribers after stop watching DOM changes停止观看 DOM 更改后删除事件侦听器/订阅者
  • Useful to watch DOM change happened in a framework, eg, Angular用于观察框架中发生的 DOM 更改,例如 Angular
import { fromEvent, timer} from 'rxjs';
import { debounceTime, takeUntil, tap } from 'rxjs/operators';

function observeDOMChange(el, options={}) {
  options = Object.assign({debounce: 100, expires: 2000}, options);

  const observer = new MutationObserver(list =>  {
    el.dispatchEvent(new CustomEvent('dom-change', {detail: list}));
  });
  observer.observe(el, {attributes: false, childList: true, subtree: true });

  let pipeFn;
  if (options.expires) {
    setTimeout(_ => observer.disconnect(), options.expires);
    pipeFn = takeUntil(timer(options.expires));
  } else {
    pipeFn = tap(_ => _); 
  }

  return fromEvent(el, 'dom-change')
    .pipe(pipeFn, debounceTime(options.debounce));
}

Demo at stackblitz. stackblitz 上的演示。
在此处输入图片说明

Use TrackChanges for detect html changes.使用 TrackChanges 检测 html 更改。 Link: https://www.npmjs.com/package/track-changes-js链接: https : //www.npmjs.com/package/track-changes-js

Example例子

 let button = document.querySelector('.button');

 trackChanges.addObserver('buttonObserver', () => button);
 
 trackChanges.addHandler('buttonObserver', buttonHandler);

 function buttonHandler(button) {
   console.log(`Button created: ${button}`);
 }

Found the question so updating with a solution in 2022.发现问题,因此在 2022 年更新解决方案。

We have seen different solutions which mostly involve MutationObserver .我们已经看到了主要涉及MutationObserver的不同解决方案。

If anyone wants to record the DOM changes and store them to replay after some time, they can use rrweb如果有人想记录 DOM 更改并存储它们以在一段时间后重播,他们可以使用rrweb

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM