将元素添加到页面时如何通知我?


139

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

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


您是否需要检查放入页面的另一个脚本中的特定元素,或者是否检查了添加的任何元素,无论其来源如何?
Jose Faeti 2011年

1
您知道什么函数在其他人的代码中添加了元素,如果可以的话,您可以覆盖它并添加一行来触发自定义事件?
jeffreydev 2011年

Answers:


86

警告!

这个答案现在已经过时了。DOM级别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(十分之一秒)。除非您需要实时元素观察,否则就足够了。


1
乔斯,当实际满足条件时,如何结束settimeout?即,您找到了元素,该元素最终在x秒后加载到您的页面上?
klewis 2013年

1
@blachawk,您需要将setTimeout返回值分配给一个变量,您稍后可以将其作为参数传递给clearTimeout()以清除它。
Jose Faeti

3
@blackhawk:我刚刚看到了这个答案,并对其进行了+1;但是您会意识到实际上不需要在这里使用clearTimeout(),因为setTimeout()仅运行一次!一个'if-else'就足够了:一旦找到插入的元素,就不要让执行传递给setTimeout()。
1111161171159459134 2014年

使用有用的实用指南MutationObserver()davidwalsh.name/mutationobserver-api
gfullam

4
太脏了 真的没有AS3的“等效”产品Event.ADDED_TO_STAGE吗?
Bitterblue


19

之间的贬值 突变事件突变事件的出现之间MutationObserver,当向DOM中添加特定元素时通知的一种有效方式是利用CSS3动画事件

引用博客文章:

设置一个CSS关键帧序列,该序列以(希望通过选择CSS选择器为目标)任何想要接收DOM节点插入事件的DOM元素为目标。我使用了相对温和且很少使用的css属性,为避免与预期的页面样式混淆,我使用了大纲颜色(coutline-color),该代码曾经以clip属性为目标,但是从IE 11版本开始,它就不再是动画了。说,任何可以设置动画的属性都可以使用,请选择您喜欢的任何一个。

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


这是性能最高的解决方案,在一个重复的问题中一个答案,提到了为此的库。
Dan Dascalescu 2015年

1
被低估的答案。
格克汗库尔特

小心。如果毫秒精度很重要,则此方法远非准确。这jsbin表明,有一个内嵌的回调和使用之间的时间超过30ms不同的是animationstartjsbin.com/netuquralu/1/edit
朱斯

我同意kurt,这被低估了。
Zabbu

13

您可以将livequery插件用于jQuery。您可以提供选择器表达式,例如:

$("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


11

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内容,因此要注意兼容性问题(例如,如果您的受众主要是Amish,luddite或更糟的是IE8用户)

首先,我们将使用MutationObserver API构造一个观察者对象。我们将把这个对象包装在一个resolve()Promise中,并在触发回调时(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>表达式时,它将以非阻塞方式等待诺言被执行。实现(生成器做的比这更多,但这是我们在这里感兴趣的)。

// 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突变集成到同步样式的控制流中。怎么回事



3

看看这个插件确实可以做到-jquery.initialize

它就像.each函数一样工作,不同之处在于它使用您输入的选择器,并在以后监视与该选择器匹配的新项并对其进行初始化

初始化看起来像这样

$(".some-element").initialize( function(){
    $(this).css("color", "blue");
});

但是现在,如果新的元素匹配.some-element选择器出现在页面上,它将被立即初始化。

添加新项目的方式并不重要,您无需关心任何回调等。

因此,如果您要添加新元素,例如:

$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!

它将立即被初始化。

插件基于 MutationObserver


0

纯JavaScript解决方案(无jQuery):

const SEARCH_DELAY = 100; // in ms

// it may run indefinitely. TODO: make it cancellable, using Promise's `reject`
function waitForElementToBeAdded(cssSelector) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      if (element = document.querySelector(cssSelector)) {
        clearInterval(interval);
        resolve(element);
      }
    }, SEARCH_DELAY);
  });
}

console.log(await waitForElementToBeAdded('#main'));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.