当DOM元素添加到页面时,我希望能运行我选择的某个功能。这是在浏览器扩展上下文中,所以网页独立于我运行,我不能修改它的源代码。在这里我有什么选择?
我想,理论上,我可以使用setInterval()
不断搜索元素的存在,并在元素出现时执行我的操作,但我需要更好的方法。
当DOM元素添加到页面时,我希望能运行我选择的某个功能。这是在浏览器扩展上下文中,所以网页独立于我运行,我不能修改它的源代码。在这里我有什么选择?
我想,理论上,我可以使用setInterval()
不断搜索元素的存在,并在元素出现时执行我的操作,但我需要更好的方法。
警告!
这个答案已经过时了。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(十分之一)秒。除非您需要实时元素观察,否则这应该足够了。
MutationObserver()
的实用实践指南:https://davidwalsh.name/mutationobserver-api - gfullamEvent.ADDED_TO_STAGE
的“等价物”吗? - BitterblueMutationObserver
出现之间,一个有效的方法来通知特定元素已被添加到DOM中是通过利用CSS3动画事件。animationstart
之间超过30毫秒的差异,https://jsbin.com/netuquralu/1/edit。 - Gajus预计于 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)
}
}
Translated
您可以在不使用库的情况下完成这项任务,但您需要使用一些ES6技术,因此需要注意兼容性问题(即,如果您的受众主要是Amish、卢德派,或更糟糕的是IE8用户)。
首先,我们将使用MutationObserver API构建一个观察者对象。我们将把这个对象封装在一个Promise中,并在回调被触发时使用resolve()
(参考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 <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")
}
关于生成器的一个棘手问题是它们不像普通函数一样“返回”。因此,我们将使用一个辅助函数来使生成器能够像常规函数一样使用。(再次致谢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 变化集成到同步风格的控制流中。这不错吧。
(请参见底部的更新答案)
你可以使用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
将事件绑定到父容器。
请查看这个插件,它可以完美实现您所需的功能 - jquery.initialize
它的工作方式与.each函数完全相同,唯一的区别是它接受您输入的选择器,并监视以后匹配此选择器的新项目并对其进行初始化。
Initialize的示例代码如下:
$(".some-element").initialize( function(){
$(this).css("color", "blue");
});
但是,如果页面上出现了与.some-element
选择器匹配的新元素,则会立即初始化该元素。
添加新项的方式并不重要,您不需要关心任何回调等。
因此,如果您添加了新元素,例如:
$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!
它将立即初始化。
插件基于MutationObserver
。
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);
}
});