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

时间:2018-09-21 13:51:59

标签: javascript mutation-observers

我需要使用class="kendoTooltip"定位所有元素,并对具有该类的任何元素运行jQuery函数。

一个html的例子是

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

我一直在尝试使用变异观察器,但无法定位页面中较深的元素。

使用以下代码,我得到了最接近的代码,但问题似乎是变异观察者仅监视音符本身及其直接子元素。我也尝试过递归调用它,但没有成功。

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

4 个答案:

答案 0 :(得分:0)

注意mutation.target将是三个值之一

  

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

     

对于属性,它是属性已更改的元素。

     

对于characterData,它是CharacterData节点。

     

对于childList,这是其子项已更改的节点。

因此,在您的情况下,target将是body,或者如果您的html实际上被添加到其他元素,则它将是该其他元素。因此,您当前检查target的classList的代码将无法正常工作,因为它不是您添加的元素,而是您将其添加到的元素。

如果要检查已添加的元素,请在addedNodes列表中的每个元素上使用addingNodes属性和搜索方法,例如querySelectorAll,getElementsByClassName等,以查找目标元素。

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

答案 1 :(得分:0)

这是一篇过时的文章,但我想我想补充一下我的想法,在尝试检测body元素内部元素的更改时,我也遇到了类似的问题。

我并不是说我的解决方案无论如何都是好的,但是看到这个问题没有被接受的答案,也许我的想法可以帮助找到解决方案。

我的问题是我想创建一个JavaScript解决方案来检测ReactJS应用程序所使用的<div id="root"></div>元素内部的插入,为什么要这样做的原因并不重要。

我发现将MutationObserver附加到documentdocument.documentElementdocument.body上并没有提供任何解决方案。我最终不得不将我的MutationObserver放入循环中,并将其附加到body中的每个子元素上,以便可以检测到更改。

这是我的代码,我敢肯定有更好的方法可以做到这一点,但我还没有找到它:

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

我希望这会有所帮助。

答案 2 :(得分:0)

为了记录,与某些评论中所说的相反,subtree 选项将针对所有后代:被观察元素的孩子,孩子的孩子......但仅限于那些直接从 DOM 添加或删除。请参阅 herehere。顺便说一下,有一个陷阱。想象一下,您想观察一个 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,
});

触发突变的代码

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

答案 3 :(得分:-1)

我很确定您不能像事件那样委派MutationObserver(尽管,公平地说,我从未实际尝试过)。我认为您需要为每个元素创建一个新的。

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

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

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

});

就其价值而言,在添加renderTooltip类时触发isRendered函数要容易得多,而不必担心MutationObserver