简体   繁体   English

使用变异观察器跟踪页面中的所有元素

[英]Track all elements in a page with mutation observer

I need to target all elements with class="kendoTooltip" and run a jQuery function on any element with that class.我需要使用class="kendoTooltip"定位所有元素,并在具有该类的任何元素上运行 jQuery 函数。

An example of the html is; html的一个例子是;

<div class="document_wrapper">
    <div>
        <div class="kendoTooltip">Tooltip!</div>
    </div>
</div>

I have been trying to use the mutation observer but have't been able to target the elements deep within the page.我一直在尝试使用变异观察器,但无法定位页面深处的元素。

With the following code, I have gotten the closest but the issue seems to be that the mutation observer only monitors the note itself and its immediate child elements.使用下面的代码,我得到了最接近的但问题似乎是变异观察者只监视音符本身及其直接子元素。 I have also tried calling it recursively but with no success.我也试过递归调用它,但没有成功。

var mutationObserver = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
        if (!mutation.target.classList.contains('isRendered') && mutation.target.classList.contains('kendoTooltip')) {
            renderTooltip(mutation.target);
            mutation.target.classList.add('isRendered');
        }
    });
});

mutationObserver.observe(document.documentElement || document.body, {
    childList: true,
    subtree: true
});

Note mutation.target is going to be one of three values注意mutation.target将是三个值之一

https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord#Properties https://developer.mozilla.org/en-US/docs/Web/API/MutationRecord#Properties

For attributes, it is the element whose attribute changed.对于属性,它是属性改变的元素。

For characterData, it is the CharacterData node.对于 characterData,它是 CharacterData 节点。

For childList, it is the node whose children changed.对于 childList,它是其子节点发生变化的节点。

So in your case target is either going to be body or if your html is actually being added to some other element it will be that other element.因此,在您的情况下, target要么是body要么如果您的 html 实际上被添加到某个其他元素,则它将是该其他元素。 Due to this your current code of checking target 's classList won't work since it is not the elements you added but the element you added them to.因此,您当前检查target的 classList 的代码将不起作用,因为它不是您添加的元素,而是您将它们添加到的元素。

If you want to check elements that have been added use the addedNodes property and a search method like querySelectorAll, getElementsByClassName, or etc on each of the elements in the addedNodes list for the elements you want to target.如果您想检查已添加的元素,请使用 addedNodes 属性和搜索方法(如 querySelectorAll、getElementsByClassName 等)对您要定位的元素的 addedNodes 列表中的每个元素。

var addedNodes = Array.from(mutation.addedNodes);

addedNodes.forEach(function(node) {
  //skip textnodes
  if (node.nodeType == 3) return;
  //findall kendoTooltips that are not
  //isRendered
  var tooltips = node.querySelectorAll('.kendoTooltip:not(.isRendered)')
                     .forEach(renderTooltip);
});

 function renderTooltip(target) { console.log("tooltiping: ", target.textContent); target.classList.add("isRendered"); } var mutationObserver = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { var addedNodes = Array.from(mutation.addedNodes); addedNodes.forEach(function(node) { //skip textnodes if (node.nodeType == 3) return; //findall kendoTooltips that are not //isRendered var tooltips = node.querySelectorAll('.kendoTooltip:not(.isRendered)') .forEach(renderTooltip); }); }); }); mutationObserver.observe(document.documentElement || document.body, { childList: true, subtree: true }); setTimeout(() => { document.body.insertAdjacentHTML('beforeend', ` <div class="document_wrapper"> <div> <div class="kendoTooltip">Tooltip!</div> </div> </div>`); }, 1000); setTimeout(() => { document.body.insertAdjacentHTML('beforeend', ` <div class="anotherclass"> <div> <div class="kendoTooltip">More Tooltip!</div> </div> </div>`); }, 3000);

This is an old post but I thought I would add my thoughts, I too have had a similar issue with trying to detect changes to an element that is inside the body element.这是一篇旧帖子,但我想我会添加我的想法,我也有类似的问题,试图检测body元素内的元素的变化。

I am not saying that my solution is good by any means but seeing as this question has no accepted answer perhaps my thoughts can help to find a solution.我并不是说我的解决方案无论如何都很好,但是看到这个问题没有公认的答案,也许我的想法可以帮助找到解决方案。

My issue is that I want to create a JavaScript solution to detecting insertions inside of the <div id="root"></div> element used by ReactJS applications, the reasons as to why I want to do this are not important.我的问题是我想创建一个 JavaScript 解决方案来检测 ReactJS 应用程序使用的<div id="root"></div>元素内的插入,我为什么要这样做的原因并不重要。

I found that attaching my MutationObserver to document , document.documentElement or document.body didn't provide any solutions.我发现将MutationObserver附加到documentdocument.documentElementdocument.body并没有提供任何解决方案。 I ended up having to place my MutationObserver inside of a loop and attach it to each child element within body so I could detect the changes.我最终不得不将我的MutationObserver放在一个循环中,并将其附加到body每个子元素,以便我可以检测到变化。

Here is my code, I am sure there is a better way to do this but I have not found it yet:这是我的代码,我确信有更好的方法可以做到这一点,但我还没有找到:

// Public function
export function listenForDynamicComponents() {

    // Create the observer
    const observer = new MutationObserver((mutations) => {

        // Loop each mutation
        for (let i = 0; i < mutations.length; i++) {
            const mutation = mutations[i];

            // Check the mutation has added nodes
            if (mutation.addedNodes.length > 0) {
                const newNodes = mutation.addedNodes;

                // Loop though each of the added nodes
                for (let j = 0; j < newNodes.length; j++) {
                    const node = newNodes[j];

                    // Check if current node is a component
                    if (node.nodeType === 1 && node.hasAttribute('data-module')) {
                        // Do something here
                    }

                    // Loop though the children of each of the added nodes
                    for (let k = 0; k < node.childNodes.length; k++) {
                        const childNode = node.childNodes[k];

                        // Check if current child node is a compoennt
                        if (childNode.nodeType === 1 && childNode.hasAttribute('data-module')) {
                            // Do something here
                        }
                    }
                }
            }
        }
    });

    // Get all nodes within body
    const bodyNodes = document.body.childNodes;

    // Loop though all child nodes of body
    for (let i = 0; i < bodyNodes.length; i++) {
        const node = bodyNodes[i];

        // Observe on each child node of body
        observer.observe(node, {
            attributes: false,
            characterData: false,
            childList: true,
            subtree: true,
            attributeOldValue: false,
            characterDataOldValue: false
        });
    }
}

I hope this helps.我希望这有帮助。

For the record, contrary to what have been said in some comments subtree option will target all descendants : children, children of children... of the observed element but only those directly added or removed from the DOM.根据记录,与某些评论中所说的相反, subtree选项将针对观察元素的所有后代:孩子,孩子的孩子......但仅限于直接从 DOM 添加或删除的那些。 See here and here .请参阅 此处此处 There is a pitfall by the way.顺便说一下,有一个陷阱。 Imagine you want to observe a video tag and its is not directly attached to the DOM but attached to a detached DOM, say a div.video-container and that it is that container which is added to the DOM.想象一下,你想要观察一个video标签,它不是直接附加到 DOM 而是附加到一个分离的 DOM,比如div.video-container ,并且它是添加到 DOM 的容器。

const mutationInstance = new MutationObserver((mutations) => {
  // The <video> is not directly added to the DOM, so wee need to query it on each added nodes
  const videoContainer = mutations
    .reduce((addedNodes, mutationRecord) => {
      return [
        ...addedNodes,
        ...(Array.from(mutationRecord.addedNodes)),
      ];
    }, [])
    .find((element) => {
      return element.querySelector("video") !== null;
    });
   
   const videoElement = videoContainer.querySelector("video");
   console.log('Video element is', videoElement);
});

mutationInstance.observe(document, {
  subtree: true,
  childList: true,
});

Code triggering the mutation触发突变的代码

const div = document.createElement('div');
div.classList.add('video-container');
const video = document.createElement('video');

// This **does not trigger** a mutation on the document
div.appendChild(video);

// This **triggers** a mutation on the document
document.body.appendChild(div);

I'm pretty sure you can't delegate a MutationObserver like you can with an event (although, to be fair, I've never actually tried it).我很确定你不能像在事件中那样委派MutationObserver (尽管公平地说,我从未真正尝试过)。 I think you need to create a new one for every element.我认为您需要为每个元素创建一个新元素。

document.querySelectorAll(".kendoTooltip").forEach(function (tooltip) {

    var mutationObserver = new MutationObserver(function (mutations) {
        // ...
    });

    mutationObserver.observe(tooltip, {
        // ...
    });

});

For what it's worth, it would be a lot easier to trigger that renderTooltip function as you add the isRendered class without having to worry about the MutationObserver就其价值而言,在添加isRendered类时触发该renderTooltip函数会容易isRendered而不必担心MutationObserver

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

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