如何确定动态创建的 DOM 元素是否已添加到 DOM 中
How can I determine if a dynamically-created DOM element has been added to the DOM?
根据规范,只有BODY
和FRAMESET
元素提供要附加到的"onload"事件,但我想知道何时将动态创建的DOM元素添加到JavaScript中的DOM中。
我目前正在使用的超级幼稚启发式方法,其工作方式如下:
- 遍历元素的 parentNode 属性,直到我找到最终祖先(即 parentNode.parentNode.parentNode.etc,直到 parentNode 为空)
- 如果最终祖先具有定义的非空主体属性
- 假设有问题的元素是 DOM 的一部分
- else
- 在 100 毫秒内再次重复这些步骤
所追求的是确认我正在做的事情是足够的(同样,它在IE7和FF3中都有效),或者一个更好的解决方案,无论出于何种原因,我都完全忘记了;也许我应该检查其他属性,等等。
编辑:我想要一种与浏览器无关的方式来做到这一点,不幸的是,我不生活在一个浏览器的世界; 也就是说,浏览器特定的信息是值得赞赏的,但请注意你知道它确实在哪个浏览器工作。谢谢!
更新:对于任何对此感兴趣的人,这是我最终使用的实现:
function isInDOMTree(node) {
// If the farthest-back ancestor of our node has a "body"
// property (that node would be the document itself),
// we assume it is in the page's DOM tree.
return !!(findUltimateAncestor(node).body);
}
function findUltimateAncestor(node) {
// Walk up the DOM tree until we are at the top (parentNode
// will return null at that point).
// NOTE: this will return the same node that was passed in
// if it has no ancestors.
var ancestor = node;
while(ancestor.parentNode) {
ancestor = ancestor.parentNode;
}
return ancestor;
}
我想要这样做的原因是提供一种为 DOM 元素合成 onload
事件的方法。这是该函数(尽管我使用的东西略有不同,因为我将其与MochiKit结合使用):
function executeOnLoad(node, func) {
// This function will check, every tenth of a second, to see if
// our element is a part of the DOM tree - as soon as we know
// that it is, we execute the provided function.
if(isInDOMTree(node)) {
func();
} else {
setTimeout(function() { executeOnLoad(node, func); }, 100);
}
}
例如,可以按如下方式使用此设置:
var mySpan = document.createElement("span");
mySpan.innerHTML = "Hello world!";
executeOnLoad(mySpan, function(node) {
alert('Added to DOM tree. ' + node.innerHTML);
});
// now, at some point later in code, this
// node would be appended to the document
document.body.appendChild(mySpan);
// sometime after this is executed, but no more than 100 ms after,
// the anonymous function I passed to executeOnLoad() would execute
希望这对某人有用。
注意:我最终得到这个解决方案而不是 Darryl 的答案的原因是,getElementById 技术仅在您在同一文档中时才有效;我在页面上有一些 iframe,页面以一些复杂的方式相互通信 - 当我尝试这样做时,问题是它找不到该元素,因为它是与它正在执行的代码不同的文档的一部分。
最直接的答案是使用Chrome,Firefox(Gecko),Internet Explorer,Opera和Safari支持的Node.contains
方法。下面是一个示例:
var el = document.createElement("div");
console.log(document.body.contains(el)); // false
document.body.appendChild(el);
console.log(document.body.contains(el)); // true
document.body.removeChild(el);
console.log(document.body.contains(el)); // false
理想情况下,我们会使用 document.contains(el)
,但这在 IE 中不起作用,所以我们使用 document.body.contains(el)
.
不幸的是,您仍然需要轮询,但是检查元素是否在文档中非常简单:
setTimeout(function test() {
if (document.body.contains(node)) {
func();
} else {
setTimeout(test, 50);
}
}, 50);
如果您同意向页面添加一些 CSS,这里有另一种聪明的技术,它使用动画来检测节点插入: http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/
你能不能做一个document.getElementById('newElementId');
,看看它是否返回 true。如果没有,就像你说的那样,等待 100 毫秒,然后再试一次?
与其将 DOM 树向上移动到文档元素,只需使用 element.ownerDocument
即可。看这里: https://developer.mozilla.org/en-US/docs/DOM/Node.ownerDocument并这样做:
element.ownerDocument.body.contains(element)
你很好。
MutationObserver 是您应该用来检测元素何时添加到 DOM 中的东西。MutationObservers现在在所有现代浏览器(Chrome 26+,Firefox 14+,IE11,Edge,Opera 15+等)上得到广泛支持。
下面是一个简单的示例,说明如何使用MutationObserver
侦听将元素添加到 DOM 时。
为了简洁起见,我使用 jQuery 语法来构建节点并将其插入到 DOM 中。
var myElement = $("<div>hello world</div>")[0];
var observer = new MutationObserver(function(mutations) {
if (document.contains(myElement)) {
console.log("It's in the DOM!");
observer.disconnect();
}
});
observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
$("body").append(myElement); // console.log: It's in the DOM!
每当在document
中添加或删除任何节点时,都会触发observer
事件处理程序。在处理程序内部,我们执行contains
检查以确定myElement
现在是否在document
中。
您不需要遍历存储在mutations
中的每个 MutationRecord,因为您可以直接在myElement
时执行document.contains
检查。
若要提高性能,请将document
替换为将在 DOM 中包含myElement
的特定元素。
您可以查询 document.getElementsByTagName("*").length 或创建自定义 appendChild 函数,如下所示:
var append = function(parent, child, onAppend) {
parent.appendChild(child);
if (onAppend) onAppend(child);
}
//inserts a div into body and adds the class "created" upon insertion
append(document.body, document.createElement("div"), function(el) {
el.className = "created";
});
更新
根据要求,将我的评论中的信息添加到我的帖子中
John Resig 今天在 Ajaxian 上的 Peppy 库上发表了一篇评论,似乎表明他的 Sizzle 库可能能够处理 DOM 插入事件。我很好奇是否也会有代码来处理IE
在轮询的想法之后,我读到某些元素属性在元素附加到文档之前不可用(例如 element.scrollTop),也许您可以轮询它而不是执行所有 DOM 遍历。
最后一件事:在IE中,一种可能值得探索的方法是使用onpropertychange事件。我认为将其附加到文档中一定会触发至少一个属性的事件。
在一个完美的世界里,你可以钩住突变事件。我怀疑它们即使在标准浏览器上也能可靠地工作。听起来您已经实现了一个突变事件,因此您可以添加它以使用本机突变事件,而不是在支持这些事件的浏览器上进行超时轮询。
我已经看到过 DOM 更改实现,它通过将document.body.innerHTML
与上次保存的.innerHTML
进行比较来监视更改,这并不完全优雅(但有效)。由于您最终要检查是否已添加特定节点,因此最好只检查每个中断。
我能想到的改进是
.offsetParent
而不是.parentNode
,因为它可能会将一些父母排除在您的循环之外compareDocumentIndex()
并测试IE .sourceIndex
属性(如果节点不在DOM中,则应为-1)。
这也可能有助于:X 浏览器比较 John Resig 的 DocumentIndex 实现。
你想要DOMNodeInserted
事件(或DOMNodeInsertedIntoDocument
)。
编辑:IE完全可能不支持这些事件,但我现在无法测试。
这是从jQuery代码中提取的问题的另一种解决方案。以下函数可用于检查节点是否已连接到 DOM 或是否"断开连接"。
函数是断开连接(节点) { 返回节点 ||!node.parentNode ||node.parentNode.nodeType === 11;}
nodeType === 11 定义一个DOCUMENT_FRAGMENT_NODE,该节点是未连接到文档对象的节点。
有关更多信息,请参阅John Resig的以下文章:http://ejohn.org/blog/dom-documentfragments/
var finalElement=null;
我正在循环中构建 dom 对象,当循环处于最后一个循环并且创建lastDomObject
时:
finalElement=lastDomObject;
离开循环:
while (!finalElement) { } //this is the delay...
下一个操作可以使用任何新创建的 dom 对象。它在第一次尝试中就奏效了。
IE 中不支持 DOMNodeInsert 或 DOMNodeInsertedIntoDocument 事件。DOM 的 IE [only] 功能中缺少的另一个功能是 compareDocumentPosition 函数,该函数旨在执行其名称所暗示的操作。
至于建议的解决方案,我认为没有更好的解决方案(除非你做一些肮脏的技巧,比如覆盖本机 DOM 成员实现)
另外,更正问题的标题:动态创建的元素从创建之日起就是 DOM 的一部分,它可以附加到文档片段或另一个元素,但可能不会附加到文档中。
我也认为你描述的问题不是你遇到的问题,这个问题看起来是你问题的潜在解决方案之一。你能详细解释一下用例吗?
- 向动态生成的DOM添加Angular自定义指令
- 通过AJAX侦听向DOM添加某些元素
- 是否可以加快向DOM添加一组元素的速度
- 通过访问DOM添加功能
- angular2:使用*ngIf构建svg图标不会给DOM添加任何内容
- 如何在声明后运行DOM添加的指令
- 在对象创建器中向 DOM 添加选项
- 向 DOM 添加新元素时出现问题
- AngularJS:如何从服务向 dom 添加和编译指令
- 使用 MathJax 向 DOM 添加元素
- 向 DOM 添加一个简单的图像标记,而不是使用 GWT 控件以获得更好的 JS 运行时性能
- IE在通过Javascript向DOM添加大量HTML时太慢
- JavaScript/CSS:向DOM添加元素和应用其CSS规则之间的延迟
- 在向DOM添加元素时调用Meteor助手函数
- Angularjs在单击时向DOM添加元素
- 如何避免第一行被删除,同时使用DOM添加和删除html表行
- 动态地向dom添加事件侦听器
- 在每次迭代时向DOM添加一个元素,而不是在循环结束时添加全部元素
- 如何在向DOM添加新元素后重新加载函数
- 动态地向DOM添加和删除样式表