简体   繁体   中英

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.

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

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

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.

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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