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

发布于 2024-12-04 19:03:15 字数 149 浏览 1 评论 0原文

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

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

I want a function of my choosing to run when a DOM element is added to the page. This is in the context of a browser extension, so the webpage runs independently of me and I cannot modify its source. What are my options here?

I guess that, in theory, I could just use setInterval() to continually search for the element's presence and perform my action if the element is there, but I need a better approach.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

我喜欢麦丽素 2024-12-11 19:03:15

警告!

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

Warning!

This answer is now outdated. DOM Level 4 introduced MutationObserver, providing an effective replacement for the deprecated mutation events. See this answer to another question for a better solution than the one presented here. Seriously. Don't poll the DOM every 100 milliseconds; it will waste CPU power and your users will hate you.

Since mutation events were deprecated in 2012, and you have no control over the inserted elements because they are added by someone else's code, your only option is to continuously check for them.

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

Once this function is called, it will run every 100 milliseconds, which is 1/10 (one tenth) of a second. Unless you need real-time element observation, it should be enough.

一紙繁鸢 2024-12-11 19:03:15

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

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

The actual answer is "use mutation observers" (as outlined in this question: Determining if a HTML element has been added to the DOM dynamically), however support (specifically on IE) is limited (http://caniuse.com/mutationobserver).

So the actual ACTUAL answer is "Use mutation observers.... eventually. But go with Jose Faeti's answer for now" :)

我乃一代侩神 2024-12-11 19:03:15

突变事件的弃用和< a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="noreferrer">MutationObserver,当特定元素添加到 DOM 时收到通知的有效方法是 利用 CSS3 动画事件

引用博文:

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

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

Between the deprecation of mutation events and the emergence of MutationObserver, an efficent way to be notified when a specific element was added to the DOM was to exploit CSS3 animation events.

To quote the blog post:

Setup a CSS keyframe sequence that targets (via your choice of CSS selector) whatever DOM elements you want to receive a DOM node insertion event for. I used a relatively benign and little used css property, clip I used outline-color in an attempt to avoid messing with intended page styles – the code once targeted the clip property, but it is no longer animatable in IE as of version 11. That said, any property that can be animated will work, choose whichever one you like.

Next I added a document-wide animationstart listener that I use as a delegate to process the node insertions. The animation event has a property called animationName on it that tells you which keyframe sequence kicked off the animation. Just make sure the animationName property is the same as the keyframe sequence name you added for node insertions and you’re good to go.

小瓶盖 2024-12-11 19:03:15

预计到达时间 2017 年 4 月 24 日
我想用一些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)
    }
}

原始

您可以在没有库的情况下执行此操作,但是您' d 必须使用一些 ES6 的东西,所以要认识到兼容性问题(即,如果你的受众主要是阿米什人、勒德分子或更糟糕的是 IE8 用户)

首先,我们将使用 MutationObserver API 用于构造观察者对象。我们将这个对象包装在一个承诺中,并在回调被触发时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);
    })
} 

然后,我们将创建一个生成器函数。如果您还没有使用过这些,那么您就错过了 - 但简单的概要是:它像同步函数一样运行,当它找到 yield 表达式时,它以非阻塞方式等待承诺的履行(生成器的作用远不止于此,但这正是我们感兴趣的)。

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

ETA 24 Apr 17
I wanted to simplify this a bit with some async/await magic, as it makes it a lot more succinct:

Using the same 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);
   })
} 

Your calling function can be as simple as:

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

If you wanted to add a timeout, you could use a simple Promise.race pattern as demonstrated here:

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

Original

You can do this without libraries, but you'd have to use some ES6 stuff, so be cognizant of compatibility issues (i.e., if your audience is mostly Amish, luddite or, worse, IE8 users)

First, we'll use the MutationObserver API to construct an observer object. We'll wrap this object in a promise, and resolve() when the callback is fired (h/t davidwalshblog)david walsh blog article on mutations:

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

Then, we'll create a generator function. If you haven't used these yet, then you're missing out--but a brief synopsis is: it runs like a sync function, and when it finds a yield <Promise> expression, it waits in a non-blocking fashion for the promise to be fulfilled (Generators do more than this, but this is what we're interested in here).

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

A tricky part about generators is they don't 'return' like a normal function. So, we'll use a helper function to be able to use the generator like a regular function. (again, h/t to 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 );
            }
        }
    })();
}

Then, at any point before the expected DOM mutation might happen, simply run runGenerator(getMutation).

Now you can integrate DOM mutations into a synchronous-style control flow. How bout that.

会傲 2024-12-11 19:03:15

(请参阅底部重新访问的答案)

您可以使用 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

(see revisited answer at the bottom)

You can use livequery plugin for jQuery. You can provide a selector expression such as:

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

Every time a button of a removeItemButton class is added a message appears in a status bar.

In terms of efficiency you might want avoid this, but in any case you could leverage the plugin instead of creating your own event handlers.

Revisited answer

The answer above was only meant to detect that an item has been added to the DOM through the plugin.

However, most likely, a jQuery.on() approach would be more appropriate, for example:

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

If you have dynamic content that should respond to clicks for example, it's best to bind events to a parent container using jQuery.on.

瑾兮 2024-12-11 19:03:15

看看这个插件,它确实做到了这一点 - 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

Check out this plugin that does exacly that - jquery.initialize

It works exacly like .each function, the difference is it takes selector you've entered and watch for new items added in future matching this selector and initialize them

Initialize looks like this

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

But now if new element matching .some-element selector will appear on page, it will be instanty initialized.

The way new item is added is not important, you dont need to care about any callbacks etc.

So if you'd add new element like:

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

it will be instantly initialized.

Plugin is based on MutationObserver

燃情 2024-12-11 19:03:15

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

A pure javascript solution (without 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'));
记忆之渊 2024-12-11 19:03:15

使用 jQuery,你可以做 -

function nodeInserted(elementQuerySelector){
    if ($(elementQuerySelector).length===0){
        setTimeout(function(){
            nodeInserted(elementQuerySelector);
        },100);
    }else{
        $(document).trigger("nodeInserted",[elementQuerySelector]);
    }
}

该函数递归搜索节点,直到找到它,然后针对文档触发一个事件

然后你可以使用它来实现它

nodeInserted("main");
$(document).on("nodeInserted",function(e,q){
    if (q === "main"){
        $("main").css("padding-left",0);
    }
});

With jQuery you can do -

function nodeInserted(elementQuerySelector){
    if ($(elementQuerySelector).length===0){
        setTimeout(function(){
            nodeInserted(elementQuerySelector);
        },100);
    }else{
        $(document).trigger("nodeInserted",[elementQuerySelector]);
    }
}

The function search recursively for the node until it finds it then trigger an event against the document

Then you can use this to implement it

nodeInserted("main");
$(document).on("nodeInserted",function(e,q){
    if (q === "main"){
        $("main").css("padding-left",0);
    }
});
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文