如何在 MutationObserver 中添加的节点上使用 querySelectorAll
how to use querySelectorAll on the added nodes in a MutationObserver
我试图在 MutationObserver
中创建一个 querySelectorAll
函数,所以它就像调用 querySelectorAll
到新添加的元素。这样做的原因是它可以与其他现有代码一起使用。
事实证明,如果不使用硬编码选择器和使用 if
语句,这很难实现,我想到了以下方法,但都失败了:
- 尝试使用已添加节点的父节点的
querySelectorAll
,但它会包含不只是添加的元素。
- 使用添加的节点
querySelectorAll
函数并合并所有结果,但它不起作用,因为不包括添加的节点本身。
- 创建一个新元素并将所有添加的节点移动到它,然后对该元素调用
querySelectorAll
,但是在 MutationObserver
运行后节点消失并且不会被添加。
有没有办法做到这一点,或者对我想出的其中一种方法进行一些修改以使其有效?
addedNodes
是一个 NodeList 集合。您可以通过调用 Array.prototype.filter
来实现与 querySelectorAll
几乎相同的东西,其中回调检查给定元素 .matches
是否通过了选择器:
new MutationObserver((mutationsList) => {
const { addedNodes } = mutationsList[0];
const matches = [...addedNodes]
.filter(node => node.nodeType === 1)
.filter(element => element.matches('.someDiv'));
if (matches.length) {
console.log(matches);
}
})
.observe(document.body, { childList: true });
setTimeout(() => {
document.body.insertAdjacentHTML(
'beforeend',
`<div class="someDiv">dynamically added matching</div>
<div class="nonMatching">dynamically added non-matching</div>`
);
}, 1000);
<div class="somediv">existing</div>
只需将传递给 .matches
的选择器字符串替换为您要过滤的任何选择器字符串。
如果你想检查addedNodes
的任何子元素是否匹配选择器,而不仅仅是addedNodes
本身,你可以使用一些东西像 flatMap
从每个元素中提取 sub-matches 的数组,如果你想:
new MutationObserver((mutationsList) => {
const { addedNodes } = mutationsList[0];
const elements = [...addedNodes]
.filter(node => node.nodeType === 1);
const matches = [
...elements.filter(element => element.matches('.someDiv')),
...elements.flatMap(element => [...element.querySelectorAll('.someDiv')])
];
if (matches.length) {
console.log(matches);
}
})
.observe(document.body, { childList: true });
setTimeout(() => {
document.body.insertAdjacentHTML(
'beforeend',
`<div class="someDiv">dynamically added matching
<div class="someDiv">dynamically added matching nested</div>
</div>
<div class="nonMatching">dynamically added non-matching</div>`
);
}, 1000);
<div class="somediv">existing</div>
我相信您知道,您的回调接收到一个 MutationRecord
数组,每个数组都有一个 NodeList
个添加的节点,称为 addedNodes
.
将它们变成与选择器匹配的元素列表可能比理想情况下更复杂,但这是一种方法(参见评论):
function applySelector(selector, records) {
// We can't create a NodeList; let's use a Set
const result = new Set();
// Loop through the records...
for (const {addedNodes} of records) {
for (const node of addedNodes) {
// If it's an element...
if (node.nodeType === 1) {
// Add it if it's a match
if (node.matches(selector)) {
result.add(node);
}
// Add any children
addAll(result, node.querySelectorAll(selector));
}
}
}
return [...result]; // Result is an array, or just return the set
}
实例:
const ob = new MutationObserver(records => {
const result = applySelector("span", records);
console.log(`Got ${result.length} matches:`);
for (const span of result) {
console.log(span.id);
}
});
const target = document.getElementById("target");
ob.observe(target, {childList: true});
target.insertAdjacentHTML(
"beforeend",
`<div>
blah
<span id="span1">span</span>
blah
<div>
<span id="span2">lorem <span id="span3">ipsum</span></span>
</div>
</div>`
);
function addAll(set, list) {
for (const entry of list) {
set.add(entry);
}
}
function applySelector(selector, records) {
// We can't create a NodeList; let's use a Set
const result = new Set();
// Loop through the records...
for (const {addedNodes} of records) {
for (const node of addedNodes) {
// If it's an element...
if (node.nodeType === 1) {
// Add it if it's a match
if (node.matches(selector)) {
result.add(node);
}
// Add any children
addAll(result, node.querySelectorAll(selector));
}
}
}
return [...result]; // Result is an array, or just return the set
}
<div id="target"></div>
我试图在 MutationObserver
中创建一个 querySelectorAll
函数,所以它就像调用 querySelectorAll
到新添加的元素。这样做的原因是它可以与其他现有代码一起使用。
事实证明,如果不使用硬编码选择器和使用 if
语句,这很难实现,我想到了以下方法,但都失败了:
- 尝试使用已添加节点的父节点的
querySelectorAll
,但它会包含不只是添加的元素。 - 使用添加的节点
querySelectorAll
函数并合并所有结果,但它不起作用,因为不包括添加的节点本身。 - 创建一个新元素并将所有添加的节点移动到它,然后对该元素调用
querySelectorAll
,但是在MutationObserver
运行后节点消失并且不会被添加。
有没有办法做到这一点,或者对我想出的其中一种方法进行一些修改以使其有效?
addedNodes
是一个 NodeList 集合。您可以通过调用 Array.prototype.filter
来实现与 querySelectorAll
几乎相同的东西,其中回调检查给定元素 .matches
是否通过了选择器:
new MutationObserver((mutationsList) => {
const { addedNodes } = mutationsList[0];
const matches = [...addedNodes]
.filter(node => node.nodeType === 1)
.filter(element => element.matches('.someDiv'));
if (matches.length) {
console.log(matches);
}
})
.observe(document.body, { childList: true });
setTimeout(() => {
document.body.insertAdjacentHTML(
'beforeend',
`<div class="someDiv">dynamically added matching</div>
<div class="nonMatching">dynamically added non-matching</div>`
);
}, 1000);
<div class="somediv">existing</div>
只需将传递给 .matches
的选择器字符串替换为您要过滤的任何选择器字符串。
如果你想检查addedNodes
的任何子元素是否匹配选择器,而不仅仅是addedNodes
本身,你可以使用一些东西像 flatMap
从每个元素中提取 sub-matches 的数组,如果你想:
new MutationObserver((mutationsList) => {
const { addedNodes } = mutationsList[0];
const elements = [...addedNodes]
.filter(node => node.nodeType === 1);
const matches = [
...elements.filter(element => element.matches('.someDiv')),
...elements.flatMap(element => [...element.querySelectorAll('.someDiv')])
];
if (matches.length) {
console.log(matches);
}
})
.observe(document.body, { childList: true });
setTimeout(() => {
document.body.insertAdjacentHTML(
'beforeend',
`<div class="someDiv">dynamically added matching
<div class="someDiv">dynamically added matching nested</div>
</div>
<div class="nonMatching">dynamically added non-matching</div>`
);
}, 1000);
<div class="somediv">existing</div>
我相信您知道,您的回调接收到一个 MutationRecord
数组,每个数组都有一个 NodeList
个添加的节点,称为 addedNodes
.
将它们变成与选择器匹配的元素列表可能比理想情况下更复杂,但这是一种方法(参见评论):
function applySelector(selector, records) {
// We can't create a NodeList; let's use a Set
const result = new Set();
// Loop through the records...
for (const {addedNodes} of records) {
for (const node of addedNodes) {
// If it's an element...
if (node.nodeType === 1) {
// Add it if it's a match
if (node.matches(selector)) {
result.add(node);
}
// Add any children
addAll(result, node.querySelectorAll(selector));
}
}
}
return [...result]; // Result is an array, or just return the set
}
实例:
const ob = new MutationObserver(records => {
const result = applySelector("span", records);
console.log(`Got ${result.length} matches:`);
for (const span of result) {
console.log(span.id);
}
});
const target = document.getElementById("target");
ob.observe(target, {childList: true});
target.insertAdjacentHTML(
"beforeend",
`<div>
blah
<span id="span1">span</span>
blah
<div>
<span id="span2">lorem <span id="span3">ipsum</span></span>
</div>
</div>`
);
function addAll(set, list) {
for (const entry of list) {
set.add(entry);
}
}
function applySelector(selector, records) {
// We can't create a NodeList; let's use a Set
const result = new Set();
// Loop through the records...
for (const {addedNodes} of records) {
for (const node of addedNodes) {
// If it's an element...
if (node.nodeType === 1) {
// Add it if it's a match
if (node.matches(selector)) {
result.add(node);
}
// Add any children
addAll(result, node.querySelectorAll(selector));
}
}
}
return [...result]; // Result is an array, or just return the set
}
<div id="target"></div>