【问题标题】:Track all elements in a page with mutation observer使用突变观察者跟踪页面中的所有元素
【发布时间】:2018-09-21 13:51:58
【问题描述】:

我需要使用 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
});

【问题讨论】:

  • 你没有观察到什么突变?即您发布的内容没有突变,所以当然什么也不会发生
  • @JaromandaX 我需要跟踪何时创建了具有 kendoTooltip 类的元素。据我所知,childElements 会跟踪添加节点的时间。
  • 您没有显示任何突变,只是静态 HTML - 它本身不会发生突变
  • 您的代码检查target,它始终是直接父元素。您需要在每个突变中枚举 addedNodes 数组,并检查您要查找的元素是否嵌套在任何这些节点中!请参阅thisthat
  • @wOxxOm 有道理,谢谢!

标签: javascript mutation-observers


【解决方案1】:

这是一篇旧帖子,但我想我会补充一下我的想法,我也遇到过类似的问题,试图检测 body 元素内的元素的更改。

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

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

我发现将我的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】:

    作为记录,与某些 cmets 中所说的相反,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】:

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

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

      对于属性,是属性发生变化的元素。

      对于characterData,它是CharacterData节点。

      对于childList,就是子节点发生变化的节点。

      因此,在您的情况下,target 要么是 body,要么如果您的 html 实际上被添加到其他元素,它将是其他元素。因此,您当前检查 target 的 classList 的代码将不起作用,因为它不是您添加的元素,而是您添加它们的元素。

      如果您想检查已添加的元素,请在 addedNodes 列表中的每个元素上使用 addedNodes 属性和搜索方法(如 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);

      【讨论】:

        【解决方案4】:

        我很确定您不能像在活动中那样委派MutationObserver(尽管公平地说,我从未真正尝试过)。我认为您需要为每个元素创建一个新元素。

        document.querySelectorAll(".kendoTooltip").forEach(function (tooltip) {
        
            var mutationObserver = new MutationObserver(function (mutations) {
                // ...
            });
        
            mutationObserver.observe(tooltip, {
                // ...
            });
        
        });
        

        对于它的价值,当您添加 isRendered 类时触发 renderTooltip 函数会容易得多,而不必担心 MutationObserver

        【讨论】:

        • 谢谢,詹姆斯。使用突变观察器的原因是,当我点击另一个页面时,我想跟踪使用“kendoTooltip”类添加的所有元素并重新运行该函数。持续跟踪所有元素的唯一方法是递归调用每个子元素的变异观察者。否则,这将是一个很好的解决方案。
        • @JaromandaX 将 'subtree' 设置为 true 将仅针对根目标的子元素,而不是子元素的子元素。这是基于我所做的工作,我不知道有什么方法可以做到这一点。
        • @JaromandaX 只有当元素发生变化时才会发生突变?我的印象是当一个元素在刷新时加载到屏幕上时会发生突变?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-01-28
        • 1970-01-01
        • 1970-01-01
        • 2020-03-23
        • 1970-01-01
        • 2014-08-12
        • 1970-01-01
        相关资源
        最近更新 更多