当一个元素被添加到页面时,我如何得到通知?

IT技术 javascript dom event-handling mutation-observers change-notification
2021-01-16 00:20:02

我希望在将 DOM 元素添加到页面时运行我选择的功能。这是在浏览器扩展的上下文中,因此网页独立于我运行,我无法修改其源。我在这里有哪些选择?

我想,理论上,我可以setInterval()用来不断搜索元素的存在并在元素存在时执行我的操作,但我需要一个更好的方法。

6个回答

警告!

这个答案现在已经过时了。DOM Level 4 引入了MutationObserver,为已弃用的突变事件提供了有效的替代。请参阅另一个问题的答案,以获得比此处提供的更好的解决方案。严重地。不要每 100 毫秒轮询一次 DOM;它会浪费 CPU 能力,您的用户会讨厌您。

由于突变事件在 2012 年已被弃用,并且您无法控制插入的元素,因为它们是由其他人的代码添加的,因此您唯一的选择是不断检查它们。

function checkDOMChange()
{
    // check for any new element being inserted here,
    // or a particular node being modified

    // call the function again after 100 milliseconds
    setTimeout( checkDOMChange, 100 );
}

一旦调用此函数,它将每 100 毫秒运行一次,即 1/10(十分之一)秒。除非你需要实时元素观察,否则应该足够了。

那很脏。真的没有与 AS3 的“等效”Event.ADDED_TO_STAGE吗?
2021-03-16 00:20:02
一个有用的实用指南MutationObserver()davidwalsh.name/mutationobserver-api
2021-03-23 00:20:02
@blachawk 您需要将 setTimeout 返回值分配给一个变量,稍后您可以将其作为参数传递给 clearTimeout() 以清除它。
2021-03-29 00:20:02
jose,当条件真正满足时,你如何结束你的settimeout?即,您找到了最终在 x 秒后加载到您的页面上的元素?
2021-04-03 00:20:02
@blackhawk :我刚看到那个答案并+1;但是你会意识到你实际上不需要在这里使用 clearTimeout() ,因为 setTimeout() 只运行一次!一个“if-else”就足够了:一旦找到插入的元素,就不要让执行传递给 setTimeout()。
2021-04-07 00:20:02

实际答案是“使用突变观察者”(如本问题所述:确定 HTML 元素是否已动态添加到 DOM 中),但是支持(特别是在 IE 上)是有限的(http://caniuse.com/mutationobserver) .

所以实际的 ACTUAL 答案是“使用突变观察者......最终。但现在使用 Jose Faeti 的答案”:)

突变事件的弃用MutationObserverDOM的出现之间,当特定元素被添加到 DOM 时,一种有效的通知方式是利用 CSS3 动画事件

引用博文:

设置一个 CSS 关键帧序列,以(通过您选择的 CSS 选择器)您想要接收 DOM 节点插入事件的任何 DOM 元素。我使用了一个相对温和且很少使用的 css 属性 clip我使用了outline-color 试图避免混淆预期的页面样式 - 代码曾经针对 clip 属性,但从版本 11 开始,它在 IE 中不再具有动画效果。说,任何可以动画的属性都可以使用,选择你喜欢的任何一个。

接下来,我添加了一个文档范围的 animationstart 侦听器,用作处理节点插入的委托。动画事件有一个名为 animationName 的属性,它告诉您哪个关键帧序列开始了动画。只需确保 animationName 属性与您为节点插入添加的关键帧序列名称相同,您就可以开始了。

被低估的答案。
2021-03-12 00:20:02
小心。如果毫秒精度很重要,那么这种方法远非准确。这jsbin表明,有一个内嵌的回调和使用之间的时间超过30ms不同的是animationstartjsbin.com/netuquralu/1/edit
2021-03-15 00:20:02
我同意库尔特的观点,这被低估了。
2021-03-15 00:20:02
这是最高性能的解决方案,在一个重复的问题中一个答案,提到了一个库。
2021-03-28 00:20:02

ETA 24 Apr 17 我想用一些async/await魔法来简化一下,因为它使它更简洁:

使用相同的promisified-observable:

const startObservable = (domNode) => {
  var targetNode = domNode;

  var observerConfig = {
    attributes: true,
    childList: true,
    characterData: true
  };

  return new Promise((resolve) => {
      var observer = new MutationObserver(function (mutations) {
         // For the sake of...observation...let's output the mutation to console to see how this all works
         mutations.forEach(function (mutation) {
             console.log(mutation.type);
         });
         resolve(mutations)
     });
     observer.observe(targetNode, observerConfig);
   })
} 

您的调用函数可以很简单:

const waitForMutation = async () => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {
      const results = await startObservable(someDomNode)
      return results
    } catch (err) { 
      console.error(err)
    }
}

如果你想添加一个超时,你可以使用一个简单的Promise.race模式如下证明

const waitForMutation = async (timeout = 5000 /*in ms*/) => {
    const button = document.querySelector('.some-button')
    if (button !== null) button.click()
    try {

      const results = await Promise.race([
          startObservable(someDomNode),
          // this will throw after the timeout, skipping 
          // the return & going to the catch block
          new Promise((resolve, reject) => setTimeout(
             reject, 
             timeout, 
             new Error('timed out waiting for mutation')
          )
       ])
      return results
    } catch (err) { 
      console.error(err)
    }
}

原来的

你可以在没有库的情况下做到这一点,但你必须使用一些 ES6 的东西,所以要意识到兼容性问题(即,如果你的观众主要是阿米什人、卢德派或更糟的是 IE8 用户)

首先,我们将使用MutationObserver API来构造一个观察者对象。我们将这个对象包装在一个Promise中,resolve()当回调被触发时 (h/t davidwalshblog) david walsh 博客文章关于突变

const startObservable = (domNode) => {
    var targetNode = domNode;

    var observerConfig = {
        attributes: true,
        childList: true,
        characterData: true
    };

    return new Promise((resolve) => {
        var observer = new MutationObserver(function (mutations) {
            // For the sake of...observation...let's output the mutation to console to see how this all works
            mutations.forEach(function (mutation) {
                console.log(mutation.type);
            });
            resolve(mutations)
        });
        observer.observe(targetNode, observerConfig);
    })
} 

然后,我们将创建一个generator function. 如果你还没有使用过这些,那么你就错过了——但一个简短的概要是:它像同步函数一样运行,当它找到一个yield <Promise>表达式时,它以非阻塞方式等待Promise实现了(生成器做的远不止这些,但这是我们在这里感兴趣的)。

// we'll declare our DOM node here, too
let targ = document.querySelector('#domNodeToWatch')

function* getMutation() {
    console.log("Starting")
    var mutations = yield startObservable(targ)
    console.log("done")
}

关于生成器的一个棘手部分是它们不像普通函数那样“返回”。因此,我们将使用辅助函数来像使用常规函数一样使用生成器。(再次,h/t 到 dwb

function runGenerator(g) {
    var it = g(), ret;

    // asynchronously iterate over generator
    (function iterate(val){
        ret = it.next( val );

        if (!ret.done) {
            // poor man's "is it a promise?" test
            if ("then" in ret.value) {
                // wait on the promise
                ret.value.then( iterate );
            }
            // immediate value: just send right back in
            else {
                // avoid synchronous recursion
                setTimeout( function(){
                    iterate( ret.value );
                }, 0 );
            }
        }
    })();
}

然后,在预期的 DOM 突变可能发生之前的任何时候,只需运行runGenerator(getMutation).

现在您可以将 DOM 更改集成到同步样式的控制流中。那怎么办。

您可以使用livequeryjQuery 插件。您可以提供一个选择器表达式,例如:

$("input[type=button].removeItemButton").livequery(function () {
    $("#statusBar").text('You may now remove items.');
});

每次removeItemButton添加一个的按钮时,都会在状态栏中显示一条消息。

就效率而言,您可能希望避免这种情况,但在任何情况下,您都可以利用插件而不是创建自己的事件处理程序。

重温答案

上面的答案仅用于检测通过插件将项目添加到 DOM

但是,最有可能的jQuery.on()方法是更合适方法,例如:

$("#myParentContainer").on('click', '.removeItemButton', function(){
          alert($(this).text() + ' has been removed');
});

例如,如果您有应该响应点击的动态内容,最好使用jQuery.on.