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

我希望在向页面添加 DOM 元素时运行我选择的函数。这是在一个浏览器扩展的上下文,所以网页运行独立于我,我不能修改它的来源。我还有什么选择?

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

177549 次浏览

警告!

这个答案现在已经过时了。DOM Level 4引入了 变异观察者,为废弃的突变事件提供了有效的替代。请参阅 这个答案到另一个问题以获得比这里提到的更好的解决方案。说真的。不要每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(十分之一秒)秒。除非您需要实时的元素观察,否则这应该足够了。

(见底部的回答)

你可以为 jQuery 使用 livequery插件,你可以提供一个选择器表达式,比如:

$("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将事件绑定到父容器。

实际的答案是“使用突变观察者”(正如这个问题中概述的: 确定是否动态地向 DOM 添加了 HTML 元素) ,但是支持(特别是 IE)是有限的(http://caniuse.com/mutationobserver)。

因此,实际的答案是“最终使用变异观察员... ... 但是现在就用 Jose Faeti 的答案”:)

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

引用这篇博文的话:

设置一个 CSS 关键帧序列,该序列(通过选择 CSS 选择器)针对您希望接收 DOM 节点插入事件的任何 DOM 元素。我使用大纲-颜色试图避免与预期的页面样式混乱-代码曾经针对剪辑属性,但它不再是可动画在 IE 版本11。也就是说,任何可以动画的属性都可以工作,选择任何你喜欢的属性。

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

看看这个插件就是这样做的-初始化

它的工作原理就像。每个函数,不同的是它采取选择器您已经输入和观察新项目添加在未来匹配这个选择器和初始化它们

初始化是这样的

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

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

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

所以如果你添加新的元素,比如:

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

它会被立即初始化。

插件是基于 MutationObserver

预计到达时间4月24日17日 我想用一些 async/await魔术来简化一下,因为它使它更加简洁:

使用同样的允诺-可观测:

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来构造一个观察者对象。我们将这个对象包装在一个承诺中,并且在触发回调时使用 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。如果您还没有使用这些表达式,那么您就错过了——但是一个简短的概要是: 它像 sync 函数一样运行,当它找到一个 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")
}

生成器的一个棘手之处在于它们不像普通函数那样“返回”。因此,我们将使用一个 helper 函数来像使用常规函数一样使用生成器。(同样,这里是 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 突变集成到同步样式的控制流中。

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

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