【问题标题】:How can I determine if a dynamically-created DOM element has been added to the DOM?如何确定是否已将动态创建的 DOM 元素添加到 DOM?
【发布时间】:2008-10-20 22:38:38
【问题描述】:

According to spec,只有BODYFRAMESET 元素提供了一个“onload”事件来附加,但我想知道动态创建的 DOM 元素何时被添加到 JavaScript 中的 DOM。

我目前使用的超朴素启发式方法如下:

  • 向后遍历元素的 parentNode 属性,直到找到最终的祖先(即 parentNode.parentNode.parentNode.etc 直到 parentNode 为空)

  • 如果最终祖先具有已定义的非空 body 属性

    • 假设有问题的元素是 dom 的一部分
  • 其他

    • 在 100 毫秒内再次重复这些步骤

我所追求的要么是确认我所做的是足够的(同样,它在 IE7 和 FF3 中都有效),要么是一个更好的解决方案,无论出于何种原因,我完全没有注意到;也许我应该检查其他属性等。


编辑:我想要一种与浏览器无关的方法,不幸的是,我并不生活在一个浏览器的世界中;也就是说,感谢您提供特定于浏览器的信息,但请注意您知道它在哪个浏览器中确实工作。谢谢!

【问题讨论】:

  • 您怎么会不知道某些内容何时添加到您的文档中?您不是该页面的作者吗?
  • @Sergey:你抽象的东西越多,一只手不知道另一只手在做什么的可能性就越大。
  • 完全正确 - 我正在构建一个“小部件”,它需要在内部知道它何时被添加到 DOM - 但由于我不一定是消费者,我不知道什么时候,因为客户端代码将确定这一点。
  • IMG 的 onload 事件不是 W3C 规范的一部分。此外,我希望这个功能不仅仅用于图像,所以它对我没有多大好处,而且会更脆弱。
  • 浏览器有一种方法可以确定一个节点是否在另一个节点内:Node.contains。在下面添加了答案:stackoverflow.com/a/13725984/101869

标签: javascript dom


【解决方案1】:

更新:对于任何对此感兴趣的人,这是我最终使用的实现:

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's answer 的原因是 getElementById 技术仅在您位于同一文档中时才有效;我在页面上有一些 iframe,并且页面之间以一些复杂的方式相互通信 - 当我尝试这个时,问题是它找不到元素,因为它是与它正在执行的代码不同的文档的一部分.

【讨论】:

【解决方案2】:

最直接的答案是使用 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/

【讨论】:

  • contains 不是一种文档方法 - 它位于 body 属性上。因此,document.contains 将不起作用,您必须像这样通过正文访问它:document.body.contains
  • @JasonBunting,两者都可以。 contains 是所有 Node 对象的方法。 documentdocument.body 都是 Node 对象,所有 HTML 元素也是如此。
  • 嗯,在 IE 9 中,document.contains == undefined,但是 document.body.contains != undefined。因此,情况似乎不一定如此,因此我发表了评论。 :)
  • 您发布的链接中的 hack 非常有帮助,对我来说就像一个魅力
  • @RobEvans,确实会。
【解决方案3】:

只需使用element.ownerDocument,而不是遍历 DOM 树直到文档元素。 见这里:https://developer.mozilla.org/en-US/docs/DOM/Node.ownerDocument 并这样做:

element.ownerDocument.body.contains(element)

你很好。

【讨论】:

    【解决方案4】:

    你能不能不做document.getElementById('newElementId'); 看看是否返回true。如果没有,就像你说的,等待 100 毫秒再试一次?

    【讨论】:

    • 如果没有id怎么办?那时不会工作,可能就是这种情况。否则很好的答案......也许我会看看这是否能满足我的一些需求,并使用其他方法以防我没有 id。
    • 为什么不加个id呢?您可以使用某种随机字符串添加一个唯一的 id,然后只查找那个。我知道你不喜欢 jQuery,但是使用 jQuery,你可以根据类、父级或子级轻松找到它。
    • 没错,这可能是一个好方法——不过我得考虑一下……谢谢。大声笑@我不喜欢 jQuery。我确实喜欢它,但我只是不喜欢人们认为它是所有 JavaScript 问题的答案。 :)
    • 所以,我也喜欢这个和 Borgar 的想法。我可以测试一下有问题的元素是否具有 ID 属性;如果是这样,请将其与 getElementById 一起使用,否则创建一个随机的、希望唯一的 ID 并分配它;当 getElementById 找到后,将原本不存在的 ID 移除,从那里往前走。
    • 我喜欢这个主意。很简洁。实际上可以肯定 document.getElementById() 会起作用,如果有的话。 :-)
    【解决方案5】:

    MutationObserver 用于检测元素何时添加到 DOM。 现在所有现代浏览器(Chrome 26+、Firefox 14+、IE11)都广泛支持 MutationObservers 、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 的特定元素。

    【讨论】:

      【解决方案6】:

      您可以查询 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";
      });
      

      更新

      应要求,将我的 cmets 中的信息添加到我的帖子中

      今天在 Ajaxian 的 Peppy 库上有一个 comment by John Resig,这似乎表明他的 Sizzle 库可能能够处理 DOM 插入事件。我很好奇是否会有代码来处理IE

      根据轮询的想法,我了解到某些元素属性在元素被附加到文档之前不可用(例如 element.scrollTop),也许您可​​以轮询它而不是执行所有 DOM 遍历.

      最后一件事:在 IE 中,可能值得探索的一种方法是使用 onpropertychange 事件。我认为将其附加到文档中必然会触发至少一个属性的事件。

      【讨论】:

      • 正如我在直接出现在我的问题下的 cmets 中提到的,我可能无法控制何时将对象添加到 DOM,否则我什至不会问这个问题。不过感谢您的回复!
      • 至于你的第一个想法,这并不像我需要的那样狭窄——其他代码可以添加 DOM 元素,我不得不假设当长度改变时,这是因为 正在添加我的 元素...这样的假设与我需要的确定性相反。
      • 我明白了。 John Resig 今天在 Ajaxian 上对 Peppy 库发表了评论,似乎暗示他的 Sizzle 库可能能够处理 DOM 插入事件。我很想知道是否还会有处理 IE 的代码。
      • 根据轮询的想法,我读到某些元素属性在元素被附加到文档之前不可用(例如 element.scrollTop),也许你可以轮询而不是做所有的 DOM 遍历。
      • 是的,这样的事情可能会很好,尽管我认为 Sizzle 项目只是为了提供更快的选择器实现,而不是其他。无论哪种方式,Resig 都是皮条客。
      【解决方案7】:

      在一个完美的世界中,您可以挂钩突变事件。我怀疑它们即使在标准浏览器上也能可靠地工作。听起来您已经实现了一个突变事件,因此您可以添加它以使用本机突变事件,而不是在支持这些事件的浏览器上进行超时轮询。

      我已经看到 DOM-change 实现通过比较 document.body.innerHTML 和上次保存的 .innerHTML 来监控变化,这并不完全优雅(但有效)。由于您最终要检查是否已添加特定节点,因此最好只检查每个中断。

      我能想到的改进是使用.offsetParent 而不是.parentNode,因为它可能会将一些父母排除在您的循环之外(请参阅cmets)。或者在除 IE 之外的所有设备上使用 compareDocumentIndex() 并为 IE 测试 .sourceIndex 属性(如果节点不在 DOM 中,则应为 -1)。

      这也可能有帮助:X browser compareDocumentIndex implementaion by John Resig

      【讨论】:

      • 事实证明 offsetParent 不会总是很好地工作......它并不总是回到 Document 本身,而如果节点是 DOM 的一部分,则使用 parentNode 遍历树会树;否则它最终返回 null。并非所有带有 offsetParent 的树上的路径都像这样工作。
      • 另外,offsetParent 显然不是 DOM 规范的一部分:developer.mozilla.org/En/DOM/Element.offsetParent 更重要的是,它“当元素将 style.display 设置为 'none' 时返回 null”,这会搞砸对我来说有点。
      【解决方案8】:

      您想要DOMNodeInserted 事件(或DOMNodeInsertedIntoDocument)。

      编辑:IE 完全有可能不支持这些事件,但我现在无法测试。

      【讨论】:

      • 谢谢 - 我可以假设这是 Mozilla/Firefox/Gecko 特定的习语吗?
      • 不,你没有这个权利。这是 DOM-Events 标准习语。
      • 知道了。也就是说,我认为 IE(浏览器的红发继子)不支持它。
      【解决方案9】:

      这是从 jQuery 代码中提取的问题的另一个解决方案。 以下函数可用于检查节点是否附加到 DOM 或是否“断开连接”。

      功能已断开连接(节点){ 返回!节点 || !node.parentNode || node.parentNode.nodeType === 11; }

      一个nodeType === 11 定义了一个DOCUMENT_FRAGMENT_NODE,它是一个没有连接到文档对象的节点。

      有关更多信息,请参阅 John Resig 的以下文章: http://ejohn.org/blog/dom-documentfragments/

      【讨论】:

      • 恐怕不能按预期工作:var ol = document.createElement('ol'); var li = ol.appendChild(document.createElement('li')); ol.id = '游戏时间'; isDisconnected(li); // 返回 false,我希望它返回 true document.getElementById('playtime'); // 什么都不返回
      【解决方案10】:
      var finalElement=null; 
      

      我在一个循环中构建 dom 对象,当循环在最后一个循环时,然后创建 lastDomObject

       finalElement=lastDomObject; 
      

      离开循环:

      while (!finalElement) { } //this is the delay... 
      

      下一个动作可以使用任何新创建的 dom 对象。第一次尝试就成功了。

      【讨论】:

        【解决方案11】:

        IE 中不支持 DOMNodeInsertedDOMNodeInsertedIntoDocument 事件。 DOM 的 IE [only] 特性中缺少的另一个功能是 compareDocumentPosition 函数,该函数旨在实现其名称所暗示的功能。

        至于建议的解决方案,我认为没有更好的解决方案(除非你做了一些肮脏的把戏,比如覆盖原生 DOM 成员实现)

        另外,更正问题的标题:动态创建的元素从创建的那一刻起就是 DOM 的一部分,它可以附加到文档片段或另一个元素,但它可能不会附加到文档.

        我也认为您描述的问题不是您遇到的问题,这个问题看起来是您问题的潜在解决方案之一。你能详细解释一下用例吗?

        【讨论】:

        • 感谢您的回复 - 我描述的问题我遇到的问题,但感谢您尝试读懂我的想法。 :) 我已经抽象出我的问题的细节,以至于这个问题的答案足以解决我更具体的问题。
        猜你喜欢
        • 2013-05-13
        • 2019-05-24
        • 2018-01-21
        • 2016-07-29
        • 1970-01-01
        • 1970-01-01
        • 2020-08-05
        • 2017-11-08
        • 1970-01-01
        相关资源
        最近更新 更多