最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Track all elements in a page with mutation observer - Stack Overflow

programmeradmin0浏览0评论

I need to target all elements with class="kendoTooltip" and run a jQuery function on any element with that class.

An example of the html is;

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

I need to target all elements with class="kendoTooltip" and run a jQuery function on any element with that class.

An example of the html is;

<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
});
Share Improve this question asked Sep 21, 2018 at 13:51 Oliver KOliver K 551 silver badge12 bronze badges 5
  • 2 what mutation are you not observing? i.e. what you've posted has no mutation, so of course nothing happens – Jaromanda X Commented Sep 21, 2018 at 13:57
  • @JaromandaX I need to track when an element with the class kendoTooltip is created. As far as I was aware, childElements would track when a node is added. – Oliver K Commented Sep 21, 2018 at 14:06
  • 1 you have not shown any mutations, just static HTML - which does not mutate in and of itself – Jaromanda X Commented Sep 21, 2018 at 14:08
  • 1 Your code checks target which is always the immediate parent element. You need to enumerate addedNodes array within each mutation, and check if the element you're looking for is nested inside any of those nodes! See this or that. – woxxom Commented Sep 21, 2018 at 14:09
  • @wOxxOm That makes sense, thank you! – Oliver K Commented Sep 21, 2018 at 14:13
Add a ment  | 

4 Answers 4

Reset to default 1

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.

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.

I found that attaching my MutationObserver to document, document.documentElement or document.body didn't provide any solutions. 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.

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 ponent
                    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 poennt
                        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 ments subtree option will target all descendants : children, children of children... of the observed element but only those directly added or removed from the 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.

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

Note mutation.target is going to be one of three values

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

For attributes, it is the element whose attribute changed.

For characterData, it is the CharacterData node.

For childList, it is the node whose children changed.

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. 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.

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.

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

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

发布评论

评论列表(0)

  1. 暂无评论